srs/lib/features/results/results_screen.dart
Aswin Biju 749d18192a
All checks were successful
deploy / deploy (push) Successful in 4m58s
refactor: updated UI and other trivial fixes
2026-11-05 10:13:32 +05:30

452 lines
13 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/format.dart';
import '../../core/models.dart';
import '../../core/providers.dart';
import '../../core/theme.dart';
import '../../widgets/account_sheet.dart';
import 'calendar_picker.dart';
import '../../widgets/ui.dart';
class ResultsScreen extends ConsumerWidget {
const ResultsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final date = ref.watch(selectedDateProvider);
final res = ref.watch(resultsProvider(date));
Future<void> refresh() => refreshData(context, ref, resultsProvider(date));
return Column(
children: [
ScreenHeader(
title: 'Results',
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Builder(
builder: (anchor) => RoundIcon(
Icons.calendar_month_rounded,
label: 'Calendar',
onTap: () => showCalendarPicker(anchor),
),
),
// Wider windows show the account in the sidebar.
if (!isWide(context)) ...[
const SizedBox(width: S.s10),
RoundIcon(
Icons.person_outline_rounded,
label: 'Account',
onTap: () => showAccountSheet(context, ref),
),
],
],
),
),
Expanded(
child: RefreshableView(
value: res,
onRefresh: refresh,
skeleton: _Loading(date: date),
builder: (r) => _Body(results: r, date: date),
),
),
],
);
}
}
class _DrawRow extends ConsumerWidget {
const _DrawRow({required this.date, required this.info});
final DateTime date;
final Widget info;
@override
Widget build(BuildContext context, WidgetRef ref) {
final n = ref.read(selectedDateProvider.notifier);
return Row(
children: [
Expanded(child: info),
RoundIcon(
Icons.chevron_left_rounded,
label: 'Previous draw',
size: 40,
onTap: () => n.set(addDays(date, -1)),
),
const SizedBox(width: S.s8),
RoundIcon(
Icons.chevron_right_rounded,
label: 'Next draw',
size: 40,
onTap: () => n.set(addDays(date, 1)),
),
],
);
}
}
/// Skeleton of the Results page.
class _Loading extends StatelessWidget {
const _Loading({required this.date});
final DateTime date;
@override
Widget build(BuildContext context) {
const onNavy = C.onNavy8;
Widget line(double h, Bone bone) => SizedBox(
height: h,
child: Align(alignment: Alignment.centerLeft, child: bone),
);
Widget stat() => SrsCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
line(16, const Bone(width: 72, height: 10)),
const SizedBox(height: S.s8),
line(30, const Bone(width: 96, height: 22)),
],
),
);
return PageList(
children: [
_DrawRow(
date: date,
info: Skeleton(
announce: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
line(22, const Bone(width: 120, height: 14)),
const SizedBox(height: S.s2),
line(18, const Bone(width: 180, height: 10)),
],
),
),
),
const SizedBox(height: S.s16),
Skeleton(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_HeroRow(
hero: NavyCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
line(
15,
const Bone(width: 90, height: 10, color: onNavy),
),
const SizedBox(height: S.s10),
line(
58,
const Bone(
width: 200,
height: 38,
radius: R.md,
color: onNavy,
),
),
const SizedBox(height: S.s10),
line(
18,
const Bone(width: 170, height: 10, color: onNavy),
),
],
),
),
stats: [stat(), stat()],
),
const Padding(
padding: EdgeInsets.fromLTRB(S.s2, S.s22, S.s2, S.s10),
child: SizedBox(
height: 21,
child: Align(
alignment: Alignment.centerLeft,
child: Bone(width: 64, height: 14),
),
),
),
const TileGridBone(count: 4),
],
),
),
],
);
}
}
class _Body extends StatelessWidget {
const _Body({required this.results, required this.date});
final DrawResults results;
final DateTime date;
@override
Widget build(BuildContext context) {
final r = results;
final d = r.draw;
return PageList(
children: [
_DrawRow(
date: date,
info: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(d.label, style: ts(16, w: w7)),
const SizedBox(height: S.s2),
Text(dateLong(d.date), style: ts(13, c: C.muted)),
],
),
),
const SizedBox(height: S.s16),
if (d.published) ..._published(context, r) else ..._notOut(context, r),
],
);
}
List<Widget> _published(BuildContext context, DrawResults r) {
final top = r.topShop;
return [
_HeroRow(
hero: NavyCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'TOTAL PRIZE',
style: ts(11, w: w6, c: C.softOnNavy, ls: 1.2),
),
const SizedBox(height: S.s10),
Text(
rupees(r.totalPrize),
style: ts(
40,
w: w7,
c: r.totalPrize > 0 ? C.gold : C.softOnNavy,
ls: -1,
),
),
const SizedBox(height: S.s10),
Text(
r.totalPrize > 0
? '${plural(r.winningTickets, 'winning ticket')} · ${plural(r.winningShops, 'shop')}'
: 'No winning tickets in this draw',
style: ts(13, w: w5, c: C.softOnNavy),
),
],
),
),
stats: [
StatCard(label: 'TOTAL TICKETS', value: inr(r.totalTickets)),
StatCard(
label: 'TOP SHOP',
value: top?.shop.code ?? '—',
valueColor: top == null ? C.muted : C.ink,
),
],
),
SectionTitle(
'Shops',
trailing: r.shops.isEmpty ? null : plural(r.shops.length, 'shop'),
),
if (r.shops.isEmpty)
const SrsCard(
child: EmptyState(
icon: Icons.receipt_long_outlined,
title: 'No orders for this draw',
body: 'Orders placed for your shops on this date will appear here.',
),
)
else
CardGrid(children: [for (final s in r.shops) _ShopRow(s: s)]),
];
}
List<Widget> _notOut(BuildContext context, DrawResults r) {
return [
_HeroRow(
hero: NavyCard(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.fromLTRB(S.s4, S.s4, S.s12, S.s4),
decoration: BoxDecoration(
color: C.goldSoft,
borderRadius: BorderRadius.circular(R.pill),
border: Border.all(color: C.gold.withAlpha(90)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
color: C.gold,
shape: BoxShape.circle,
),
child: const Icon(
Icons.hourglass_top_rounded,
size: 12,
color: C.navy,
),
),
const SizedBox(width: S.s8),
Text(
'AWAITING RESULTS',
style: ts(11, w: w7, c: C.gold, ls: 1.1),
),
],
),
),
const SizedBox(height: S.s12),
Text(
'Results not yet published',
style: ts(26, w: w7, c: Colors.white, ls: -0.4),
),
const SizedBox(height: S.s8),
Text(
'Official results are usually published after 4 PM.',
style: ts(13, c: C.softOnNavy, h: 1.5),
),
],
),
),
stats: [
StatCard(label: 'ORDERS', value: inr(r.orders)),
StatCard(label: 'TICKETS', value: inr(r.totalTickets)),
],
),
SectionTitle(
'Shops',
trailing: r.shops.isEmpty ? null : plural(r.shops.length, 'shop'),
),
if (r.shops.isEmpty)
const SrsCard(
child: EmptyState(
icon: Icons.inbox_outlined,
title: 'No orders',
body: 'No orders have been placed for this draw.',
),
)
else
CardGrid(
children: [for (final s in r.shops) _ShopRow(s: s, pending: true)],
),
];
}
}
/// The navy hero with its two stat cards: stacked under it on phones,
/// beside it on wide windows.
class _HeroRow extends StatelessWidget {
const _HeroRow({required this.hero, required this.stats});
final Widget hero;
final List<Widget> stats;
@override
Widget build(BuildContext context) => LayoutBuilder(
builder: (context, c) {
if (c.maxWidth < 760) {
return Column(
children: [
hero,
const SizedBox(height: S.s12),
Row(
children: [
Expanded(child: stats[0]),
const SizedBox(width: S.s12),
Expanded(child: stats[1]),
],
),
],
);
}
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(flex: 2, child: hero),
const SizedBox(width: S.s12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: stats[0]),
const SizedBox(height: S.s12),
Expanded(child: stats[1]),
],
),
),
],
),
);
},
);
}
class _ShopRow extends StatelessWidget {
const _ShopRow({required this.s, this.pending = false});
final ShopResult s;
final bool pending;
@override
Widget build(BuildContext context) {
final win = s.prize > 0;
return SrsCard(
onTap: () => context.push('/shops/${s.shop.id}'),
padding: const EdgeInsets.all(S.s14),
child: Row(
children: [
CodeTile(s.shop.code, dark: win),
const SizedBox(width: S.s14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
s.shop.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: ts(15, w: w6),
),
const SizedBox(height: S.s4),
Text(
pending
? plural(s.tickets, 'ticket')
: win
? plural(s.winTickets, 'winning ticket')
: 'No winning tickets',
style: ts(12, c: C.muted),
),
],
),
),
if (!pending)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
rupees(s.prize),
style: ts(16, w: w7, c: win ? C.green : C.muted),
),
if (win) ...[
const SizedBox(height: S.s2),
Text(
plural(s.winTickets, 'ticket'),
style: ts(11, c: C.muted),
),
],
],
)
else
const Icon(Icons.chevron_right_rounded, color: C.placeholder),
],
),
);
}
}