Skip to content

Commit 2cfd110

Browse files
committed
refactor: enhance debt detail screen layout and improve reminders section interactivity
1 parent 86d4196 commit 2cfd110

5 files changed

Lines changed: 253 additions & 186 deletions

File tree

mobile/lib/features/debts/presentation/debt_detail_screen.dart

Lines changed: 102 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -426,57 +426,61 @@ class _LoadedShell extends ConsumerWidget {
426426
final isTerminal = record.isTerminal;
427427
final canCancelDebt = (ref.watch(isMerchantOwnerProvider).valueOrNull ?? true) && !isTerminal;
428428

429-
return CustomScrollView(
430-
clipBehavior: Clip.none,
431-
physics: const AlwaysScrollableScrollPhysics(),
432-
slivers: [
433-
SliverToBoxAdapter(
434-
child: _DetailHeroWithStatusCard(
435-
customer: detail.customer,
436-
record: record,
437-
onBack: () => context.pop(),
438-
onRefresh: () =>
439-
_refreshDetailFromServer(context, ref, receivableId),
440-
canCancel: canCancelDebt,
441-
onCancel: () =>
442-
_confirmAndCancelReceivableDebt(context, ref, record),
443-
),
444-
),
445-
SliverFillRemaining(
446-
hasScrollBody: false,
447-
child: ColoredBox(
448-
color: DebtsUi.pageBackground,
449-
child: Padding(
450-
padding: const EdgeInsets.fromLTRB(14, 0, 14, 24),
451-
child: Column(
452-
crossAxisAlignment: CrossAxisAlignment.stretch,
453-
children: [
454-
const StaleBanner(
455-
screenKey: 'debt_detail',
456-
kvKey: KvCacheRepository.kDebtsTs,
457-
),
458-
const SizedBox(height: 12),
459-
DebtMetaRow(record: record),
460-
const SizedBox(height: 12),
461-
DebtCustomerSummary(customer: detail.customer),
462-
const SizedBox(height: 12),
463-
DebtRemindersSection(
464-
record: record,
465-
customerName: detail.customer.name,
466-
),
467-
const SizedBox(height: 12),
468-
DebtPaymentsHistory(payments: detail.payments),
469-
if (!isTerminal) ...[
470-
const SizedBox(height: 18),
471-
_ReceivePaymentCta(
472-
onTap: () => _handleReceivePayment(context, record),
429+
return Column(
430+
children: [
431+
Expanded(
432+
child: CustomScrollView(
433+
clipBehavior: Clip.none,
434+
physics: const AlwaysScrollableScrollPhysics(),
435+
slivers: [
436+
SliverToBoxAdapter(
437+
child: _DetailHeroWithStatusCard(
438+
customer: detail.customer,
439+
record: record,
440+
onBack: () => context.pop(),
441+
onRefresh: () =>
442+
_refreshDetailFromServer(context, ref, receivableId),
443+
canCancel: canCancelDebt,
444+
onCancel: () =>
445+
_confirmAndCancelReceivableDebt(context, ref, record),
446+
),
447+
),
448+
SliverFillRemaining(
449+
hasScrollBody: false,
450+
child: ColoredBox(
451+
color: DebtsUi.pageBackground,
452+
child: Padding(
453+
padding: const EdgeInsets.fromLTRB(14, 0, 14, 20),
454+
child: Column(
455+
crossAxisAlignment: CrossAxisAlignment.stretch,
456+
children: [
457+
const StaleBanner(
458+
screenKey: 'debt_detail',
459+
kvKey: KvCacheRepository.kDebtsTs,
460+
),
461+
const SizedBox(height: 12),
462+
DebtMetaRow(record: record),
463+
const SizedBox(height: 12),
464+
DebtCustomerSummary(customer: detail.customer),
465+
const SizedBox(height: 12),
466+
DebtRemindersSection(
467+
record: record,
468+
customerName: detail.customer.name,
469+
),
470+
const SizedBox(height: 12),
471+
DebtPaymentsHistory(payments: detail.payments),
472+
],
473473
),
474-
],
475-
],
474+
),
475+
),
476476
),
477-
),
477+
],
478478
),
479479
),
480+
if (!isTerminal)
481+
_PinnedPaymentBar(
482+
onTap: () => _handleReceivePayment(context, record),
483+
),
480484
],
481485
);
482486
}
@@ -489,47 +493,65 @@ class _LoadedShell extends ConsumerWidget {
489493
}
490494
}
491495

492-
class _ReceivePaymentCta extends StatelessWidget {
493-
const _ReceivePaymentCta({required this.onTap});
496+
/// Fixed bottom action bar — keeps "Receive Payment" reachable without
497+
/// scrolling past the cards (mirrors the mockup's fixed `.bottom-bar`).
498+
class _PinnedPaymentBar extends StatelessWidget {
499+
const _PinnedPaymentBar({required this.onTap});
494500

495501
final VoidCallback onTap;
496502

497503
@override
498504
Widget build(BuildContext context) {
499-
return SizedBox(
500-
width: double.infinity,
501-
child: Material(
502-
color: Colors.transparent,
503-
child: InkWell(
504-
borderRadius: BorderRadius.circular(DebtsUi.radiusMd),
505-
onTap: onTap,
506-
child: Container(
507-
padding: const EdgeInsets.symmetric(vertical: 16),
508-
decoration: BoxDecoration(
509-
gradient: DebtsUi.ctaGradient,
505+
return Container(
506+
decoration: const BoxDecoration(
507+
color: DebtsUi.surface,
508+
border: Border(top: BorderSide(color: DebtsUi.borderNeutral)),
509+
boxShadow: [
510+
BoxShadow(
511+
color: Color(0x0D101828),
512+
blurRadius: 20,
513+
offset: Offset(0, -6),
514+
),
515+
],
516+
),
517+
child: SafeArea(
518+
top: false,
519+
child: Padding(
520+
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
521+
child: Material(
522+
color: Colors.transparent,
523+
child: InkWell(
510524
borderRadius: BorderRadius.circular(DebtsUi.radiusMd),
511-
boxShadow: const [
512-
BoxShadow(
513-
color: Color(0x59166B42),
514-
blurRadius: 20,
515-
offset: Offset(0, 6),
525+
onTap: onTap,
526+
child: Container(
527+
padding: const EdgeInsets.symmetric(vertical: 15),
528+
decoration: BoxDecoration(
529+
gradient: DebtsUi.ctaGradient,
530+
borderRadius: BorderRadius.circular(DebtsUi.radiusMd),
531+
boxShadow: const [
532+
BoxShadow(
533+
color: Color(0x40166B42),
534+
blurRadius: 16,
535+
offset: Offset(0, 6),
536+
),
537+
],
516538
),
517-
],
518-
),
519-
child: const Row(
520-
mainAxisAlignment: MainAxisAlignment.center,
521-
children: [
522-
Icon(LucideIcons.creditCard, size: 18, color: Colors.white),
523-
SizedBox(width: 8),
524-
Text(
525-
'Receive Payment',
526-
style: TextStyle(
527-
fontSize: 15,
528-
fontWeight: FontWeight.w700,
529-
color: Colors.white,
530-
),
539+
child: const Row(
540+
mainAxisAlignment: MainAxisAlignment.center,
541+
children: [
542+
Icon(LucideIcons.creditCard, size: 18, color: Colors.white),
543+
SizedBox(width: 8),
544+
Text(
545+
'Receive Payment',
546+
style: TextStyle(
547+
fontSize: 15,
548+
fontWeight: FontWeight.w700,
549+
color: Colors.white,
550+
),
551+
),
552+
],
531553
),
532-
],
554+
),
533555
),
534556
),
535557
),

mobile/lib/features/debts/presentation/widgets/debt_meta_row.dart

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,28 @@ class DebtMetaRow extends StatelessWidget {
3232
return Column(
3333
crossAxisAlignment: CrossAxisAlignment.start,
3434
children: [
35-
Row(
36-
children: [
37-
_InfoBox(
38-
icon: Icons.receipt_long_outlined,
39-
label: 'INVOICE',
40-
value: invoice,
41-
),
42-
const SizedBox(width: 10),
43-
_InfoBox(
44-
icon: Icons.calendar_today_outlined,
45-
label: 'CREATED',
46-
value: created,
47-
),
48-
const SizedBox(width: 10),
49-
_InfoBox(
50-
icon: Icons.event_outlined,
51-
label: 'DUE',
52-
value: due,
53-
tone: isOverdue ? DebtsUi.danger : null,
54-
),
55-
],
35+
Container(
36+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
37+
decoration: BoxDecoration(
38+
color: DebtsUi.surface,
39+
borderRadius: BorderRadius.circular(DebtsUi.radiusMd),
40+
border: Border.all(color: DebtsUi.borderNeutral, width: 1.5),
41+
boxShadow: DebtsUi.shadowNeutralSm,
42+
),
43+
child: Row(
44+
crossAxisAlignment: CrossAxisAlignment.start,
45+
children: [
46+
_InfoCell(label: 'INVOICE', value: invoice),
47+
const _CellDivider(),
48+
_InfoCell(label: 'CREATED', value: created),
49+
const _CellDivider(),
50+
_InfoCell(
51+
label: 'DUE',
52+
value: due,
53+
tone: isOverdue ? DebtsUi.danger : null,
54+
),
55+
],
56+
),
5657
),
5758
if (isOverdue && daysUntilDue != null) ...[
5859
const SizedBox(height: 10),
@@ -84,72 +85,68 @@ class DebtMetaRow extends StatelessWidget {
8485
}
8586
}
8687

87-
class _InfoBox extends StatelessWidget {
88-
const _InfoBox({
89-
required this.icon,
88+
/// One cell in the light meta strip (Invoice | Created | Due).
89+
class _InfoCell extends StatelessWidget {
90+
const _InfoCell({
9091
required this.label,
9192
required this.value,
9293
this.tone,
9394
});
9495

95-
final IconData icon;
9696
final String label;
9797
final String value;
9898
final Color? tone;
9999

100100
@override
101101
Widget build(BuildContext context) {
102-
final accent = tone ?? DebtsUi.textMuted;
103102
return Expanded(
104-
child: Container(
105-
padding: const EdgeInsets.all(12),
106-
decoration: BoxDecoration(
107-
color: DebtsUi.surface,
108-
borderRadius: BorderRadius.circular(DebtsUi.radiusSm),
109-
border: Border.all(color: DebtsUi.border, width: 1.5),
110-
boxShadow: DebtsUi.shadowSm,
111-
),
112-
child: Column(
113-
crossAxisAlignment: CrossAxisAlignment.start,
114-
mainAxisSize: MainAxisSize.min,
115-
children: [
116-
Row(
117-
children: [
118-
Icon(icon, size: 11, color: accent),
119-
const SizedBox(width: 4),
120-
Flexible(
121-
child: Text(
122-
label,
123-
maxLines: 1,
124-
overflow: TextOverflow.ellipsis,
125-
style: const TextStyle(
126-
fontSize: 10,
127-
fontWeight: FontWeight.w700,
128-
letterSpacing: 0.8,
129-
color: DebtsUi.textMuted,
130-
),
131-
),
132-
),
133-
],
103+
child: Column(
104+
crossAxisAlignment: CrossAxisAlignment.start,
105+
mainAxisSize: MainAxisSize.min,
106+
children: [
107+
Text(
108+
label,
109+
maxLines: 1,
110+
overflow: TextOverflow.ellipsis,
111+
style: const TextStyle(
112+
fontSize: 10,
113+
fontWeight: FontWeight.w700,
114+
letterSpacing: 0.8,
115+
color: DebtsUi.textMuted,
134116
),
135-
const SizedBox(height: 5),
136-
Text(
137-
value,
138-
maxLines: 1,
139-
overflow: TextOverflow.ellipsis,
140-
style: TextStyle(
141-
fontSize: 13,
142-
fontWeight: FontWeight.w700,
143-
color: tone ?? DebtsUi.textPrimary,
144-
),
117+
),
118+
const SizedBox(height: 4),
119+
Text(
120+
value,
121+
maxLines: 1,
122+
overflow: TextOverflow.ellipsis,
123+
style: TextStyle(
124+
fontSize: 13,
125+
fontWeight: FontWeight.w700,
126+
color: tone ?? DebtsUi.textPrimary,
145127
),
146-
],
147-
),
128+
),
129+
],
148130
),
149131
);
150132
}
151133
}
152134

135+
/// Thin vertical divider between meta cells.
136+
class _CellDivider extends StatelessWidget {
137+
const _CellDivider();
138+
139+
@override
140+
Widget build(BuildContext context) {
141+
return Container(
142+
width: 1,
143+
height: 30,
144+
margin: const EdgeInsets.symmetric(horizontal: 12),
145+
color: DebtsUi.borderNeutral,
146+
);
147+
}
148+
}
149+
153150
class _Chip extends StatelessWidget {
154151
const _Chip({
155152
required this.icon,

0 commit comments

Comments
 (0)