refactor: convert BillingView to StatefulWidget and implement scroll listener for dynamic UI updates
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -6,7 +7,6 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../blocs/billing_bloc.dart';
|
||||
import '../blocs/billing_event.dart';
|
||||
import '../blocs/billing_state.dart';
|
||||
import '../widgets/billing_header.dart';
|
||||
import '../widgets/invoice_history_section.dart';
|
||||
import '../widgets/payment_method_card.dart';
|
||||
import '../widgets/pending_invoices_section.dart';
|
||||
@@ -34,23 +34,136 @@ class BillingPage extends StatelessWidget {
|
||||
///
|
||||
/// This widget displays the billing dashboard content based on the current
|
||||
/// state of the [BillingBloc].
|
||||
class BillingView extends StatelessWidget {
|
||||
class BillingView extends StatefulWidget {
|
||||
/// Creates a [BillingView].
|
||||
const BillingView({super.key});
|
||||
|
||||
@override
|
||||
State<BillingView> createState() => _BillingViewState();
|
||||
}
|
||||
|
||||
class _BillingViewState extends State<BillingView> {
|
||||
late ScrollController _scrollController;
|
||||
bool _isScrolled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.hasClients) {
|
||||
if (_scrollController.offset > 140 && !_isScrolled) {
|
||||
setState(() => _isScrolled = true);
|
||||
} else if (_scrollController.offset <= 140 && _isScrolled) {
|
||||
setState(() => _isScrolled = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BillingBloc, BillingState>(
|
||||
builder: (BuildContext context, BillingState state) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
BillingHeader(
|
||||
currentBill: state.currentBill,
|
||||
savings: state.savings,
|
||||
onBack: () => Modular.to.pop(),
|
||||
body: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
backgroundColor: UiColors.primary,
|
||||
leading: Center(
|
||||
child: UiIconButton.secondary(
|
||||
icon: UiIcons.arrowLeft,
|
||||
onTap: () => Modular.to.pop(),
|
||||
),
|
||||
),
|
||||
title: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
_isScrolled
|
||||
? '\$${state.currentBill.toStringAsFixed(2)}'
|
||||
: t.client_billing.title,
|
||||
key: ValueKey<bool>(_isScrolled),
|
||||
style: UiTypography.headline4m.copyWith(
|
||||
color: UiColors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: UiConstants.space0,
|
||||
left: UiConstants.space5,
|
||||
right: UiConstants.space5,
|
||||
bottom: UiConstants.space10,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_billing.current_period,
|
||||
style: UiTypography.footnote2r.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
'\$${state.currentBill.toStringAsFixed(2)}',
|
||||
style: UiTypography.display1b
|
||||
.copyWith(color: UiColors.white),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space2,
|
||||
vertical: UiConstants.space1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.accent,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.trendingDown,
|
||||
size: 12,
|
||||
color: UiColors.foreground,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
t.client_billing.saved_amount(
|
||||
amount: state.savings.toStringAsFixed(0),
|
||||
),
|
||||
style: UiTypography.footnote2b.copyWith(
|
||||
color: UiColors.foreground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
<Widget>[
|
||||
_buildContent(context, state),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: _buildContent(context, state)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -60,7 +173,10 @@ class BillingView extends StatelessWidget {
|
||||
|
||||
Widget _buildContent(BuildContext context, BillingState state) {
|
||||
if (state.status == BillingStatus.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(UiConstants.space10),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status == BillingStatus.failure) {
|
||||
@@ -72,7 +188,7 @@ class BillingView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -89,6 +205,7 @@ class BillingView extends StatelessWidget {
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
InvoiceHistorySection(invoices: state.invoiceHistory),
|
||||
const SizedBox(height: UiConstants.space24),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * 0.8),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user