Reuploading fixed qoutation
This commit is contained in:
@@ -10,7 +10,7 @@ import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
import 'package:pshared/provider/payment/provider.dart';
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
|
||||
@@ -66,9 +66,9 @@ RouteBase payoutShellRoute() => ShellRoute(
|
||||
),
|
||||
],
|
||||
child: PageSelector(
|
||||
child: child,
|
||||
routerState: state,
|
||||
),
|
||||
child: child,
|
||||
routerState: state,
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
||||
@@ -434,6 +434,29 @@
|
||||
"payout": "Payout",
|
||||
"sendTo": "Send Payout To",
|
||||
"send": "Send Payout",
|
||||
"quoteUnavailable": "Waiting for a quote...",
|
||||
"quoteUpdating": "Refreshing quote...",
|
||||
"quoteExpiresIn": "Quote expires in {time}",
|
||||
"@quoteExpiresIn": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quoteActive": "Quote is active",
|
||||
"quoteExpired": "Quote expired, request a new one",
|
||||
"quoteAutoRefresh": "Auto-refresh quote",
|
||||
"quoteAutoRefreshHint": "Keeps the quote current while you prepare the payout",
|
||||
"quoteAutoRefreshEnabled": "Auto-refresh is on",
|
||||
"quoteAutoRefreshDisabled": "Auto-refresh is off",
|
||||
"quoteEnableAutoRefresh": "Enable auto-refresh",
|
||||
"quoteDisableAutoRefresh": "Disable auto-refresh",
|
||||
"quoteRefresh": "Refresh quote",
|
||||
"quoteRefreshRequired": "Refresh the quote to enable payout",
|
||||
"quoteErrorGeneric": "Could not refresh quote, try again later",
|
||||
"toggleOn": "On",
|
||||
"toggleOff": "Off",
|
||||
"refreshBalance": "Refresh balance",
|
||||
"recipientPaysFee": "Recipient pays the fee",
|
||||
|
||||
|
||||
@@ -434,6 +434,29 @@
|
||||
"payout": "Выплата",
|
||||
"sendTo": "Отправить выплату",
|
||||
"send": "Отправить выплату",
|
||||
"quoteUnavailable": "Ожидание котировки...",
|
||||
"quoteUpdating": "Обновляем котировку...",
|
||||
"quoteExpiresIn": "Котировка истекает через {time}",
|
||||
"@quoteExpiresIn": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quoteActive": "Котировка активна",
|
||||
"quoteExpired": "Срок котировки истек, запросите новую",
|
||||
"quoteAutoRefresh": "Автообновление котировки",
|
||||
"quoteAutoRefreshHint": "Поддерживает котировку актуальной во время подготовки выплаты",
|
||||
"quoteAutoRefreshEnabled": "Автообновление включено",
|
||||
"quoteAutoRefreshDisabled": "Автообновление выключено",
|
||||
"quoteEnableAutoRefresh": "Включить автообновление",
|
||||
"quoteDisableAutoRefresh": "Выключить автообновление",
|
||||
"quoteRefresh": "Обновить котировку",
|
||||
"quoteRefreshRequired": "Обновите котировку, чтобы продолжить выплату",
|
||||
"quoteErrorGeneric": "Не удалось обновить котировку, повторите позже",
|
||||
"toggleOn": "Вкл",
|
||||
"toggleOff": "Выкл",
|
||||
"refreshBalance": "Обновить баланс",
|
||||
"recipientPaysFee": "Получатель оплачивает комиссию",
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:pshared/provider/permissions.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/accounts/employees.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
import 'package:pshared/provider/invitations.dart';
|
||||
@@ -87,10 +86,6 @@ void main() async {
|
||||
create: (_) => InvitationsProvider(),
|
||||
update: (context, organizations, provider) => provider!..updateProviders(organizations),
|
||||
),
|
||||
ChangeNotifierProxyProvider2<OrganizationsProvider, RecipientsProvider, PaymentMethodsProvider>(
|
||||
create: (_) => PaymentMethodsProvider(),
|
||||
update: (context, organizations, recipients, provider) => provider!..updateProviders(organizations, recipients),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => InvitationListViewModel(),
|
||||
),
|
||||
@@ -109,4 +104,4 @@ void main() async {
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,51 +48,54 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => PageViewLoader(
|
||||
child: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(true),
|
||||
isActive: _showContainerSingle,
|
||||
label: AppLocalizations.of(context)!.sendSingle,
|
||||
icon: Icons.person_add,
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return PageViewLoader(
|
||||
child: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(true),
|
||||
isActive: _showContainerSingle,
|
||||
label: l10n.sendSingle,
|
||||
icon: Icons.person_add,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.small),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(false),
|
||||
isActive: _showContainerMultiple,
|
||||
label: AppLocalizations.of(context)!.sendMultiple,
|
||||
icon: Icons.group_add,
|
||||
const SizedBox(width: AppSpacing.small),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: TransactionRefButton(
|
||||
onTap: () => _setActive(false),
|
||||
isActive: _showContainerMultiple,
|
||||
label: l10n.sendMultiple,
|
||||
icon: Icons.group_add,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.medium),
|
||||
BalanceWidget(
|
||||
onTopUp: widget.onTopUp,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.small),
|
||||
if (_showContainerMultiple) TitleMultiplePayout(),
|
||||
const SizedBox(height: AppSpacing.medium),
|
||||
if (_showContainerSingle)
|
||||
SinglePayoutForm(
|
||||
onRecipientSelected: widget.onRecipientSelected,
|
||||
onGoToPayment: widget.onGoToPaymentWithoutRecipient,
|
||||
],
|
||||
),
|
||||
if (_showContainerMultiple) MultiplePayoutForm(),
|
||||
],
|
||||
const SizedBox(height: AppSpacing.medium),
|
||||
BalanceWidget(
|
||||
onTopUp: widget.onTopUp,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.small),
|
||||
if (_showContainerMultiple) TitleMultiplePayout(),
|
||||
const SizedBox(height: AppSpacing.medium),
|
||||
if (_showContainerSingle)
|
||||
SinglePayoutForm(
|
||||
onRecipientSelected: widget.onRecipientSelected,
|
||||
onGoToPayment: widget.onGoToPaymentWithoutRecipient,
|
||||
),
|
||||
if (_showContainerMultiple) MultiplePayoutForm(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/amount.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/fee_payer.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/quote_status/quote_status.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
@@ -36,8 +37,9 @@ class PaymentFormWidget extends StatelessWidget {
|
||||
const SizedBox(height: _largeSpacing),
|
||||
|
||||
const PaymentSummary(spacing: _extraSpacing),
|
||||
const SizedBox(height: _mediumSpacing),
|
||||
const QuoteStatus(spacing: _smallSpacing),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/quote/status_type.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/quote_status/widgets/body.dart';
|
||||
import 'package:pweb/utils/quote_duration_format.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class QuoteStatus extends StatefulWidget {
|
||||
final double spacing;
|
||||
|
||||
const QuoteStatus({super.key, required this.spacing});
|
||||
|
||||
@override
|
||||
State<QuoteStatus> createState() => _QuoteStatusState();
|
||||
}
|
||||
|
||||
class _QuoteStatusState extends State<QuoteStatus> {
|
||||
Timer? _ticker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<QuotationProvider>();
|
||||
final timeLeft = provider.quoteTimeLeft;
|
||||
final isLoading = provider.isLoading;
|
||||
final statusType = provider.quoteStatusType;
|
||||
final autoRefreshMode = provider.autoRefreshMode;
|
||||
|
||||
String statusText;
|
||||
String? helperText;
|
||||
switch (statusType) {
|
||||
case QuoteStatusType.loading:
|
||||
statusText = loc.quoteUpdating;
|
||||
break;
|
||||
case QuoteStatusType.error:
|
||||
statusText = loc.quoteErrorGeneric;
|
||||
break;
|
||||
case QuoteStatusType.missing:
|
||||
statusText = loc.quoteUnavailable;
|
||||
break;
|
||||
case QuoteStatusType.expired:
|
||||
statusText = loc.quoteExpired;
|
||||
helperText = loc.quoteRefreshRequired;
|
||||
break;
|
||||
case QuoteStatusType.active:
|
||||
statusText = timeLeft == null
|
||||
? loc.quoteActive
|
||||
: loc.quoteExpiresIn(formatQuoteDuration(timeLeft));
|
||||
break;
|
||||
}
|
||||
|
||||
final canRefresh = provider.canRefresh && !isLoading;
|
||||
final showPrimaryRefresh = canRefresh &&
|
||||
(statusType == QuoteStatusType.expired ||
|
||||
statusType == QuoteStatusType.error ||
|
||||
statusType == QuoteStatusType.missing);
|
||||
|
||||
return QuoteStatusBody(
|
||||
spacing: widget.spacing,
|
||||
statusType: statusType,
|
||||
statusText: statusText,
|
||||
helperText: helperText,
|
||||
isLoading: isLoading,
|
||||
canRefresh: canRefresh,
|
||||
showPrimaryRefresh: showPrimaryRefresh,
|
||||
autoRefreshMode: autoRefreshMode,
|
||||
onAutoRefreshModeChanged: provider.setAutoRefreshMode,
|
||||
onRefresh: provider.refreshQuotation,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/quote/status_type.dart';
|
||||
import 'package:pshared/models/auto_refresh_mode.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/quote_status/widgets/refresh_section.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/quote_status/widgets/card.dart';
|
||||
|
||||
|
||||
class QuoteStatusBody extends StatelessWidget {
|
||||
final double spacing;
|
||||
final QuoteStatusType statusType;
|
||||
final String statusText;
|
||||
final String? helperText;
|
||||
final bool isLoading;
|
||||
final bool canRefresh;
|
||||
final bool showPrimaryRefresh;
|
||||
final AutoRefreshMode autoRefreshMode;
|
||||
final ValueChanged<AutoRefreshMode> onAutoRefreshModeChanged;
|
||||
final VoidCallback onRefresh;
|
||||
|
||||
const QuoteStatusBody({
|
||||
super.key,
|
||||
required this.spacing,
|
||||
required this.statusType,
|
||||
required this.statusText,
|
||||
required this.helperText,
|
||||
required this.isLoading,
|
||||
required this.canRefresh,
|
||||
required this.showPrimaryRefresh,
|
||||
required this.autoRefreshMode,
|
||||
required this.onAutoRefreshModeChanged,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
QuoteStatusCard(
|
||||
statusType: statusType,
|
||||
isLoading: isLoading,
|
||||
statusText: statusText,
|
||||
helperText: helperText,
|
||||
canRefresh: canRefresh,
|
||||
showPrimaryRefresh: showPrimaryRefresh,
|
||||
onRefresh: onRefresh,
|
||||
),
|
||||
SizedBox(height: spacing),
|
||||
QuoteAutoRefreshSection(
|
||||
autoRefreshMode: autoRefreshMode,
|
||||
canRefresh: canRefresh,
|
||||
onModeChanged: onAutoRefreshModeChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/quote/status_type.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class QuoteStatusCard extends StatelessWidget {
|
||||
final QuoteStatusType statusType;
|
||||
final bool isLoading;
|
||||
final String statusText;
|
||||
final String? helperText;
|
||||
final bool canRefresh;
|
||||
final bool showPrimaryRefresh;
|
||||
final VoidCallback onRefresh;
|
||||
|
||||
const QuoteStatusCard({
|
||||
super.key,
|
||||
required this.statusType,
|
||||
required this.isLoading,
|
||||
required this.statusText,
|
||||
required this.helperText,
|
||||
required this.canRefresh,
|
||||
required this.showPrimaryRefresh,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
static const double _cardRadius = 12;
|
||||
static const double _cardSpacing = 12;
|
||||
static const double _iconSize = 18;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final foregroundColor = _resolveForegroundColor(theme, statusType);
|
||||
final statusStyle = theme.textTheme.bodyMedium?.copyWith(color: foregroundColor);
|
||||
final helperStyle = theme.textTheme.bodySmall?.copyWith(
|
||||
color: foregroundColor.withValues(alpha: 0.8),
|
||||
);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(_cardSpacing),
|
||||
decoration: BoxDecoration(
|
||||
color: _resolveCardColor(theme, statusType),
|
||||
borderRadius: BorderRadius.circular(_cardRadius),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: _iconSize,
|
||||
height: _iconSize,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(foregroundColor),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
_resolveIcon(statusType),
|
||||
size: _iconSize,
|
||||
color: foregroundColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _cardSpacing),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(statusText, style: statusStyle),
|
||||
if (helperText != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(helperText!, style: helperStyle),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (canRefresh)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: _cardSpacing),
|
||||
child: showPrimaryRefresh
|
||||
? ElevatedButton(
|
||||
onPressed: canRefresh ? onRefresh : null,
|
||||
child: Text(AppLocalizations.of(context)!.quoteRefresh),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: canRefresh ? onRefresh : null,
|
||||
child: Text(AppLocalizations.of(context)!.quoteRefresh),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _resolveCardColor(ThemeData theme, QuoteStatusType status) {
|
||||
switch (status) {
|
||||
case QuoteStatusType.loading:
|
||||
return theme.colorScheme.secondaryContainer;
|
||||
case QuoteStatusType.error:
|
||||
case QuoteStatusType.expired:
|
||||
return theme.colorScheme.errorContainer;
|
||||
case QuoteStatusType.active:
|
||||
return theme.colorScheme.primaryContainer;
|
||||
case QuoteStatusType.missing:
|
||||
return theme.colorScheme.surfaceContainerHighest;
|
||||
}
|
||||
}
|
||||
|
||||
Color _resolveForegroundColor(ThemeData theme, QuoteStatusType status) {
|
||||
switch (status) {
|
||||
case QuoteStatusType.loading:
|
||||
return theme.colorScheme.onSecondaryContainer;
|
||||
case QuoteStatusType.error:
|
||||
case QuoteStatusType.expired:
|
||||
return theme.colorScheme.onErrorContainer;
|
||||
case QuoteStatusType.active:
|
||||
return theme.colorScheme.onPrimaryContainer;
|
||||
case QuoteStatusType.missing:
|
||||
return theme.colorScheme.onSurfaceVariant;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _resolveIcon(QuoteStatusType status) {
|
||||
switch (status) {
|
||||
case QuoteStatusType.loading:
|
||||
return Icons.sync_rounded;
|
||||
case QuoteStatusType.error:
|
||||
return Icons.warning_amber_rounded;
|
||||
case QuoteStatusType.expired:
|
||||
return Icons.error_outline_rounded;
|
||||
case QuoteStatusType.active:
|
||||
return Icons.timer_outlined;
|
||||
case QuoteStatusType.missing:
|
||||
return Icons.info_outline_rounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/auto_refresh_mode.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class QuoteAutoRefreshSection extends StatelessWidget {
|
||||
final AutoRefreshMode autoRefreshMode;
|
||||
final bool canRefresh;
|
||||
final ValueChanged<AutoRefreshMode> onModeChanged;
|
||||
|
||||
const QuoteAutoRefreshSection({
|
||||
super.key,
|
||||
required this.autoRefreshMode,
|
||||
required this.canRefresh,
|
||||
required this.onModeChanged,
|
||||
});
|
||||
|
||||
static const double _autoRefreshSpacing = 8;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
autoRefreshMode == AutoRefreshMode.on
|
||||
? loc.quoteAutoRefreshEnabled
|
||||
: loc.quoteAutoRefreshDisabled,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
loc.quoteAutoRefreshHint,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color?.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _autoRefreshSpacing),
|
||||
ToggleButtons(
|
||||
isSelected: [
|
||||
autoRefreshMode == AutoRefreshMode.off,
|
||||
autoRefreshMode == AutoRefreshMode.on,
|
||||
],
|
||||
onPressed: canRefresh
|
||||
? (index) {
|
||||
final nextMode =
|
||||
index == 1 ? AutoRefreshMode.on : AutoRefreshMode.off;
|
||||
if (nextMode == autoRefreshMode) return;
|
||||
onModeChanged(nextMode);
|
||||
}
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
constraints: const BoxConstraints(minHeight: 32, minWidth: 56),
|
||||
selectedColor: theme.colorScheme.onPrimary,
|
||||
fillColor: theme.colorScheme.primary,
|
||||
color: theme.colorScheme.onSurface,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(loc.toggleOff),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(loc.toggleOn),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/payment/provider.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
|
||||
@@ -54,6 +56,9 @@ class PaymentPageContent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final quotationProvider = context.watch<QuotationProvider>();
|
||||
final paymentProvider = context.watch<PaymentProvider>();
|
||||
final isSendEnabled = quotationProvider.hasLiveQuote && !paymentProvider.isLoading;
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
@@ -112,7 +117,7 @@ class PaymentPageContent extends StatelessWidget {
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
const PaymentFormWidget(),
|
||||
SizedBox(height: dimensions.paddingXXXLarge),
|
||||
SendButton(onPressed: onSend),
|
||||
SendButton(onPressed: onSend, isEnabled: isSendEnabled),
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/payment/provider.dart';
|
||||
import 'package:pshared/provider/payment/quotation/quotation.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
||||
@@ -50,6 +54,9 @@ class PaymentPageContent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final quotationProvider = context.watch<QuotationProvider>();
|
||||
final paymentProvider = context.watch<PaymentProvider>();
|
||||
final isSendEnabled = quotationProvider.hasLiveQuote && !paymentProvider.isLoading;
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
@@ -95,7 +102,7 @@ class PaymentPageContent extends StatelessWidget {
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
const PaymentFormWidget(),
|
||||
SizedBox(height: dimensions.paddingXXXLarge),
|
||||
SendButton(onPressed: onSend),
|
||||
SendButton(onPressed: onSend, isEnabled: isSendEnabled),
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -7,31 +7,43 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
class SendButton extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
final bool isEnabled;
|
||||
|
||||
const SendButton({super.key, required this.onPressed});
|
||||
const SendButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
this.isEnabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final dimensions = AppDimensions();
|
||||
|
||||
final backgroundColor = isEnabled
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.onSurface.withValues(alpha: 0.12);
|
||||
final textColor = isEnabled
|
||||
? theme.colorScheme.onSecondary
|
||||
: theme.colorScheme.onSurface.withValues(alpha: 0.38);
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: dimensions.buttonWidth,
|
||||
height: dimensions.buttonHeight,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||
onTap: onPressed,
|
||||
onTap: isEnabled ? onPressed : null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.send,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -41,4 +53,4 @@ class SendButton extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
frontend/pweb/lib/utils/quote_duration_format.dart
Normal file
11
frontend/pweb/lib/utils/quote_duration_format.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
String formatQuoteDuration(Duration duration) {
|
||||
final totalSeconds = duration.inSeconds < 0 ? 0 : duration.inSeconds;
|
||||
final hours = totalSeconds ~/ 3600;
|
||||
final minutes = (totalSeconds % 3600) ~/ 60;
|
||||
final seconds = totalSeconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return '${hours.toString()}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
return '${minutes.toString()}:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
Reference in New Issue
Block a user