Added quote expiry-aware flows with auto-refresh
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
|
||||
class QuoteStatusActions extends StatelessWidget {
|
||||
final bool isLoading;
|
||||
final bool canRefresh;
|
||||
final bool autoRefreshEnabled;
|
||||
final ValueChanged<bool>? onToggleAutoRefresh;
|
||||
final VoidCallback? onRefresh;
|
||||
final String autoRefreshLabel;
|
||||
final String refreshLabel;
|
||||
final AppDimensions dimensions;
|
||||
final TextTheme theme;
|
||||
|
||||
const QuoteStatusActions({
|
||||
super.key,
|
||||
required this.isLoading,
|
||||
required this.canRefresh,
|
||||
required this.autoRefreshEnabled,
|
||||
required this.onToggleAutoRefresh,
|
||||
required this.onRefresh,
|
||||
required this.autoRefreshLabel,
|
||||
required this.refreshLabel,
|
||||
required this.dimensions,
|
||||
required this.theme,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SwitchListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: autoRefreshEnabled,
|
||||
title: Text(autoRefreshLabel, style: theme.bodyMedium),
|
||||
onChanged: onToggleAutoRefresh,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: canRefresh ? onRefresh : null,
|
||||
icon: isLoading
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
label: Text(refreshLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
|
||||
class QuoteStatusMessage extends StatelessWidget {
|
||||
final String statusText;
|
||||
final String? errorText;
|
||||
final bool showDetails;
|
||||
final VoidCallback onToggleDetails;
|
||||
final AppDimensions dimensions;
|
||||
final TextTheme theme;
|
||||
final String showLabel;
|
||||
final String hideLabel;
|
||||
|
||||
const QuoteStatusMessage({
|
||||
super.key,
|
||||
required this.statusText,
|
||||
required this.errorText,
|
||||
required this.showDetails,
|
||||
required this.onToggleDetails,
|
||||
required this.dimensions,
|
||||
required this.theme,
|
||||
required this.showLabel,
|
||||
required this.hideLabel,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(statusText, style: theme.bodyMedium),
|
||||
if (errorText != null) ...[
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
TextButton(
|
||||
onPressed: onToggleDetails,
|
||||
child: Text(showDetails ? hideLabel : showLabel),
|
||||
),
|
||||
if (showDetails) ...[
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
Text(
|
||||
errorText!,
|
||||
style: theme.bodySmall,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/quote/actions.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/quote/message.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
|
||||
class QuoteStatus extends StatefulWidget {
|
||||
const QuoteStatus({super.key});
|
||||
|
||||
@override
|
||||
State<QuoteStatus> createState() => _QuoteStatusState();
|
||||
}
|
||||
|
||||
class _QuoteStatusState extends State<QuoteStatus> {
|
||||
bool _showDetails = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Consumer<QuotationProvider>(
|
||||
builder: (context, provider, _) {
|
||||
final statusText = _statusText(provider, loc);
|
||||
final canRefresh = provider.canRequestQuote && !provider.isLoading;
|
||||
final refreshLabel = provider.isLoading ? loc.quoteUpdating : loc.retry;
|
||||
final error = provider.error;
|
||||
final backgroundColor = theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.3);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(dimensions.paddingMedium),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
QuoteStatusMessage(
|
||||
statusText: statusText,
|
||||
errorText: error?.toString(),
|
||||
showDetails: _showDetails,
|
||||
onToggleDetails: () => setState(() => _showDetails = !_showDetails),
|
||||
dimensions: dimensions,
|
||||
theme: theme.textTheme,
|
||||
showLabel: loc.showDetails,
|
||||
hideLabel: loc.hideDetails,
|
||||
),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
QuoteStatusActions(
|
||||
isLoading: provider.isLoading,
|
||||
canRefresh: canRefresh,
|
||||
autoRefreshEnabled: provider.autoRefreshEnabled,
|
||||
onToggleAutoRefresh: provider.canRequestQuote
|
||||
? (value) => context.read<QuotationProvider>().setAutoRefresh(value)
|
||||
: null,
|
||||
onRefresh: canRefresh ? () => context.read<QuotationProvider>().refreshNow() : null,
|
||||
autoRefreshLabel: loc.quoteAutoRefresh,
|
||||
refreshLabel: refreshLabel,
|
||||
dimensions: dimensions,
|
||||
theme: theme.textTheme,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _statusText(QuotationProvider provider, AppLocalizations loc) {
|
||||
if (provider.error != null) {
|
||||
return loc.quoteErrorGeneric;
|
||||
}
|
||||
|
||||
if (!provider.canRequestQuote) {
|
||||
return loc.quoteUnavailable;
|
||||
}
|
||||
|
||||
if (provider.isLoading) {
|
||||
return loc.quoteUpdating;
|
||||
}
|
||||
|
||||
if (provider.hasLiveQuote) {
|
||||
final remaining = provider.timeToExpire;
|
||||
if (remaining != null) {
|
||||
return loc.quoteExpiresIn(_formatDuration(remaining));
|
||||
}
|
||||
}
|
||||
|
||||
if (provider.hasQuoteForCurrentIntent) {
|
||||
return loc.quoteExpired;
|
||||
}
|
||||
|
||||
return loc.quoteUnavailable;
|
||||
}
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
final minutes = duration.inMinutes;
|
||||
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
|
||||
if (duration.inHours > 0) {
|
||||
final hours = duration.inHours;
|
||||
final mins = minutes.remainder(60).toString().padLeft(2, '0');
|
||||
return '$hours:$mins:$seconds';
|
||||
}
|
||||
return '$minutes:$seconds';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user