import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
import '../../core/errors/api_error_code.dart';
import '../../core/theme/app_theme.dart';
import '../../l10n/app_localizations.dart';
import '../../models/claim_models.dart';
import '../../repositories/claim_repository.dart';
import '../../shared/states/app_state_widgets.dart';
class ClaimPage extends StatefulWidget {
const ClaimPage({super.key, required this.repository});
final ClaimRepository repository;
@override
State<ClaimPage> createState() => _ClaimPageState();
}
class _ClaimPageState extends State<ClaimPage> {
bool _loading = true;
ApiErrorCode? _errorCode;
List<ClaimRecord> _claims = const [];
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() {
_loading = true;
_errorCode = null;
});
try {
final response = await widget.repository.list();
if (!mounted) return;
setState(() {
_claims = response.claims;
_loading = false;
});
} catch (error) {
if (!mounted) return;
setState(() {
_errorCode = error is ApiException ? error.code : ApiErrorCode.unknown;
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.claimTitle),
leading: IconButton(
onPressed: () => Navigator.of(context).maybePop(),
tooltip: l10n.back,
icon: const Icon(Icons.arrow_back),
),
),
body: RefreshIndicator(
onRefresh: _load,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: _ClaimHeader(count: _claims.length, loading: _loading),
),
if (_loading)
const SliverFillRemaining(
hasScrollBody: false,
child: LoadingState(),
)
else if (_errorCode == ApiErrorCode.permissionDenied)
const SliverFillRemaining(
hasScrollBody: false,
child: AuthRequiredState(),
)
else if (_errorCode != null)
SliverFillRemaining(
hasScrollBody: false,
child: ErrorState(onRetry: _load, code: _errorCode),
)
else if (_claims.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: EmptyState(
title: l10n.claimEmptyTitle,
body: l10n.claimEmptyBody,
),
)
else
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
sliver: SliverList.separated(
itemCount: _claims.length,
itemBuilder: (context, index) => Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 860),
child: _ClaimCard(claim: _claims[index]),
),
),
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
),
),
],
),
),
);
}
}
class _ClaimHeader extends StatelessWidget {
const _ClaimHeader({required this.count, required this.loading});
final int count;
final bool loading;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 860),
child: Card(
margin: const EdgeInsets.fromLTRB(16, 16, 16, 12),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: AppTheme.palette(
context,
).brand.withValues(alpha: .12),
borderRadius: BorderRadius.circular(16),
),
child: const Padding(
padding: EdgeInsets.all(12),
child: Icon(Icons.verified_user_outlined),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.claimTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 6),
Text(
l10n.claimDescription,
style: TextStyle(
color: AppTheme.palette(context).muted,
),
),
if (!loading) ...[
const SizedBox(height: 12),
Chip(
avatar: const Icon(Icons.list_alt_outlined, size: 18),
label: Text(l10n.claimSummary(count)),
),
],
],
),
),
],
),
),
),
),
);
}
}
class _ClaimCard extends StatelessWidget {
const _ClaimCard({required this.claim});
final ClaimRecord claim;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final status = switch (claim.status) {
'approved' => l10n.claimApproved,
'rejected' => l10n.claimRejected,
_ => l10n.claimPending,
};
final statusColor = switch (claim.status) {
'approved' => AppTheme.palette(context).success,
'rejected' => Theme.of(context).colorScheme.error,
_ => AppTheme.palette(context).brand,
};
final objectType = switch (claim.objectType) {
'places' => l10n.contentTypePlace,
'events' => l10n.contentTypeEvent,
'services' => l10n.contentTypeService,
'classifieds' => l10n.contentTypeClassified,
'jobs' => l10n.contentTypeJob,
'real-estate' => l10n.contentTypeRealEstate,
_ => l10n.contentTypeOther,
};
final submittedAt = _localizedDate(context, claim.createdAt);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
claim.organizationName.isNotEmpty
? claim.organizationName
: l10n.unknownValue,
style: Theme.of(context).textTheme.titleMedium,
),
),
Chip(
avatar: Icon(
claim.status == 'approved'
? Icons.check_circle_outline
: claim.status == 'rejected'
? Icons.cancel_outlined
: Icons.schedule_outlined,
size: 17,
color: statusColor,
),
label: Text(status),
),
],
),
const SizedBox(height: 8),
_ClaimMeta(
icon: Icons.category_outlined,
label: l10n.claimObject,
value: '$objectType #${claim.objectId}',
),
if (submittedAt.isNotEmpty)
_ClaimMeta(
icon: Icons.calendar_today_outlined,
label: l10n.claimSubmittedOn,
value: submittedAt,
),
if (claim.claimantNote.isNotEmpty) ...[
const SizedBox(height: 12),
Text(
l10n.claimApplicantNote,
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 4),
Text(claim.claimantNote),
],
if (claim.decisionNote.isNotEmpty) ...[
const SizedBox(height: 12),
Text(
l10n.claimDecisionNote,
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 4),
Text(claim.decisionNote),
],
],
),
),
);
}
String _localizedDate(BuildContext context, String value) {
final date = DateTime.tryParse(value);
if (date == null) return '';
return intl.DateFormat.yMMMd(
AppLocalizations.of(context).localeName,
).format(date.toLocal());
}
}
class _ClaimMeta extends StatelessWidget {
const _ClaimMeta({
required this.icon,
required this.label,
required this.value,
});
final IconData icon;
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 18, color: AppTheme.palette(context).muted),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
color: AppTheme.palette(context).muted,
fontSize: 12,
),
),
const SizedBox(height: 2),
Text(value),
],
),
),
],
),
);
}
}
