811 lines
24 KiB
Dart
811 lines
24 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:inou_app/design/inou_theme.dart';
|
||
import 'package:inou_app/design/inou_text.dart';
|
||
import 'package:inou_app/design/widgets/widgets.dart';
|
||
import 'models.dart';
|
||
import 'mock_data.dart';
|
||
|
||
class DossierPage extends StatelessWidget {
|
||
final String dossierId;
|
||
|
||
const DossierPage({super.key, required this.dossierId});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final data = getDossierById(dossierId);
|
||
|
||
if (data == null) {
|
||
return InouAuthPage(
|
||
userName: 'Johan',
|
||
onProfileTap: () => context.go('/profile'),
|
||
onLogoutTap: () => context.go('/login'),
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Text('Dossier not found', style: InouText.sectionTitle),
|
||
const SizedBox(height: 16),
|
||
InouButton(
|
||
text: '← Back to dossiers',
|
||
variant: ButtonVariant.secondary,
|
||
onPressed: () => context.go('/dashboard'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
return InouAuthPage(
|
||
userName: 'Johan',
|
||
onProfileTap: () => context.go('/profile'),
|
||
onLogoutTap: () => context.go('/login'),
|
||
child: SingleChildScrollView(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: InouTheme.spaceXl,
|
||
vertical: InouTheme.spaceXxxl,
|
||
),
|
||
child: Center(
|
||
child: ConstrainedBox(
|
||
constraints: const BoxConstraints(maxWidth: InouTheme.maxWidthNarrow), // 800px per styleguide
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Header
|
||
_DossierHeader(data: data),
|
||
const SizedBox(height: 32),
|
||
|
||
// Data sections
|
||
_ImagingSection(studies: data.studies, dossierId: dossierId),
|
||
_LabsSection(labs: data.labs),
|
||
if (data.documents.isNotEmpty) _DocumentsSection(documents: data.documents),
|
||
if (data.procedures.isNotEmpty) _ProceduresSection(procedures: data.procedures),
|
||
if (data.assessments.isNotEmpty) _AssessmentsSection(assessments: data.assessments),
|
||
if (data.hasGenome) _GeneticsSection(categories: data.geneticCategories),
|
||
_UploadsSection(count: data.uploadCount, size: data.uploadSize, canEdit: data.canEdit),
|
||
if (data.medications.isNotEmpty) _MedicationsSection(medications: data.medications),
|
||
if (data.symptoms.isNotEmpty) _SymptomsSection(symptoms: data.symptoms),
|
||
if (data.hospitalizations.isNotEmpty) _HospitalizationsSection(hospitalizations: data.hospitalizations),
|
||
if (data.therapies.isNotEmpty) _TherapiesSection(therapies: data.therapies),
|
||
_VitalsSection(), // Coming soon
|
||
_PrivacySection(accessList: data.accessList, dossierId: dossierId, canManageAccess: data.canManageAccess),
|
||
|
||
const SizedBox(height: 48),
|
||
const InouFooter(),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DossierHeader extends StatelessWidget {
|
||
final DossierData data;
|
||
|
||
const _DossierHeader({required this.data});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(data.dossier.name, style: InouText.pageTitle),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
[
|
||
if (data.dossier.dateOfBirth != null) 'Born: ${data.dossier.dateOfBirth}',
|
||
if (data.dossier.sex != null) data.dossier.sex,
|
||
].join(' · '),
|
||
style: InouText.intro,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
InouButton(
|
||
text: '← Back to dossiers',
|
||
variant: ButtonVariant.secondary,
|
||
size: ButtonSize.small,
|
||
onPressed: () => context.go('/dashboard'),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// DATA SECTION CARDS
|
||
// ============================================
|
||
|
||
class _ImagingSection extends StatelessWidget {
|
||
final List<ImagingStudy> studies;
|
||
final String dossierId;
|
||
|
||
const _ImagingSection({required this.studies, required this.dossierId});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final totalSlices = studies.fold<int>(0, (sum, s) => sum + s.series.fold<int>(0, (ss, ser) => ss + ser.sliceCount));
|
||
|
||
return _DataCard(
|
||
title: 'IMAGING',
|
||
indicatorColor: InouTheme.indicatorImaging,
|
||
summary: studies.isEmpty
|
||
? 'No imaging data'
|
||
: '${studies.length} studies, $totalSlices slices',
|
||
trailing: studies.isNotEmpty
|
||
? InouButton(
|
||
text: 'Open viewer',
|
||
size: ButtonSize.small,
|
||
onPressed: () {
|
||
// TODO: open viewer
|
||
},
|
||
)
|
||
: null,
|
||
child: studies.isEmpty
|
||
? null
|
||
: Column(
|
||
children: [
|
||
for (var i = 0; i < studies.length && i < 5; i++)
|
||
_ImagingStudyRow(study: studies[i], dossierId: dossierId),
|
||
if (studies.length > 5)
|
||
_ShowMoreRow(
|
||
text: 'Show all ${studies.length} studies',
|
||
onTap: () {
|
||
// TODO: expand
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ImagingStudyRow extends StatefulWidget {
|
||
final ImagingStudy study;
|
||
final String dossierId;
|
||
|
||
const _ImagingStudyRow({required this.study, required this.dossierId});
|
||
|
||
@override
|
||
State<_ImagingStudyRow> createState() => _ImagingStudyRowState();
|
||
}
|
||
|
||
class _ImagingStudyRowState extends State<_ImagingStudyRow> {
|
||
bool _expanded = false;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final hasSeries = widget.study.seriesCount > 1;
|
||
|
||
return Column(
|
||
children: [
|
||
InkWell(
|
||
onTap: hasSeries ? () => setState(() => _expanded = !_expanded) : null,
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
child: Row(
|
||
children: [
|
||
if (hasSeries)
|
||
SizedBox(
|
||
width: 20,
|
||
child: Text(
|
||
_expanded ? '−' : '+',
|
||
style: InouText.mono.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
)
|
||
else
|
||
const SizedBox(width: 20),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Text(
|
||
widget.study.description,
|
||
style: InouText.body.copyWith(fontWeight: FontWeight.w500),
|
||
),
|
||
),
|
||
if (hasSeries)
|
||
Text(
|
||
'${widget.study.seriesCount} series',
|
||
style: InouText.mono,
|
||
),
|
||
const SizedBox(width: 16),
|
||
Text(
|
||
_formatDate(widget.study.date),
|
||
style: InouText.mono.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Icon(Icons.arrow_forward, size: 16, color: InouTheme.accent),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
if (_expanded)
|
||
Container(
|
||
color: InouTheme.bg,
|
||
child: Column(
|
||
children: [
|
||
for (final series in widget.study.series)
|
||
if (series.sliceCount > 0)
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
child: Row(
|
||
children: [
|
||
const SizedBox(width: 32),
|
||
Expanded(
|
||
child: Text(
|
||
series.description ?? series.modality,
|
||
style: InouText.bodySmall,
|
||
),
|
||
),
|
||
Text(
|
||
'${series.sliceCount} ${series.sliceCount == 1 ? 'slice' : 'slices'}',
|
||
style: InouText.mono,
|
||
),
|
||
const SizedBox(width: 8),
|
||
Icon(Icons.arrow_forward, size: 14, color: InouTheme.accent),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
],
|
||
);
|
||
}
|
||
|
||
String _formatDate(String yyyymmdd) {
|
||
if (yyyymmdd.length != 8) return yyyymmdd;
|
||
return '${yyyymmdd.substring(4, 6)}/${yyyymmdd.substring(6, 8)}/${yyyymmdd.substring(0, 4)}';
|
||
}
|
||
}
|
||
|
||
class _LabsSection extends StatelessWidget {
|
||
final List<DataItem> labs;
|
||
|
||
const _LabsSection({required this.labs});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'LABS',
|
||
indicatorColor: InouTheme.indicatorLabs,
|
||
summary: labs.isEmpty ? 'No lab data' : '${labs.length} results',
|
||
child: labs.isEmpty
|
||
? null
|
||
: Column(
|
||
children: [
|
||
for (final lab in labs) _DataRow(item: lab),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DocumentsSection extends StatelessWidget {
|
||
final List<DataItem> documents;
|
||
|
||
const _DocumentsSection({required this.documents});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'RECORDS',
|
||
indicatorColor: InouTheme.indicatorRecords,
|
||
summary: '${documents.length} documents',
|
||
child: Column(
|
||
children: [
|
||
for (final doc in documents) _DataRow(item: doc, showType: true),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ProceduresSection extends StatelessWidget {
|
||
final List<DataItem> procedures;
|
||
|
||
const _ProceduresSection({required this.procedures});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'PROCEDURES & SURGERY',
|
||
indicatorColor: InouTheme.danger,
|
||
summary: '${procedures.length} procedures',
|
||
child: Column(
|
||
children: [
|
||
for (final proc in procedures) _DataRow(item: proc),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _AssessmentsSection extends StatelessWidget {
|
||
final List<DataItem> assessments;
|
||
|
||
const _AssessmentsSection({required this.assessments});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'CLINICAL ASSESSMENTS',
|
||
indicatorColor: const Color(0xFF7C3AED),
|
||
summary: '${assessments.length} assessments',
|
||
child: Column(
|
||
children: [
|
||
for (final assessment in assessments) _DataRow(item: assessment),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _GeneticsSection extends StatelessWidget {
|
||
final List<GeneticCategory> categories;
|
||
|
||
const _GeneticsSection({required this.categories});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final totalShown = categories.fold<int>(0, (sum, c) => sum + c.shown);
|
||
final totalHidden = categories.fold<int>(0, (sum, c) => sum + c.hidden);
|
||
|
||
return _DataCard(
|
||
title: 'GENETICS',
|
||
indicatorColor: InouTheme.indicatorGenetics,
|
||
summary: '$totalShown variants${totalHidden > 0 ? ' ($totalHidden hidden)' : ''}',
|
||
trailing: totalHidden > 0
|
||
? InouButton(
|
||
text: 'Show all',
|
||
size: ButtonSize.small,
|
||
onPressed: () {
|
||
// TODO: show warning modal
|
||
},
|
||
)
|
||
: null,
|
||
child: Column(
|
||
children: [
|
||
for (final cat in categories.take(5))
|
||
if (cat.shown > 0)
|
||
_GeneticCategoryRow(category: cat),
|
||
if (categories.length > 5)
|
||
_ShowMoreRow(
|
||
text: 'Show all ${categories.length} categories',
|
||
onTap: () {},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _GeneticCategoryRow extends StatelessWidget {
|
||
final GeneticCategory category;
|
||
|
||
const _GeneticCategoryRow({required this.category});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
child: Row(
|
||
children: [
|
||
const SizedBox(
|
||
width: 20,
|
||
child: Text('+', style: TextStyle(color: InouTheme.textMuted)),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Text(
|
||
category.displayName,
|
||
style: InouText.body.copyWith(fontWeight: FontWeight.w500),
|
||
),
|
||
),
|
||
Text(
|
||
'${category.shown} variants${category.hidden > 0 ? ' (${category.hidden} hidden)' : ''}',
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _UploadsSection extends StatelessWidget {
|
||
final int count;
|
||
final String size;
|
||
final bool canEdit;
|
||
|
||
const _UploadsSection({required this.count, required this.size, required this.canEdit});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'UPLOADS',
|
||
indicatorColor: InouTheme.indicatorUploads,
|
||
summary: count == 0 ? 'No files' : '$count files, $size',
|
||
trailing: InouButton(
|
||
text: 'Manage',
|
||
size: ButtonSize.small,
|
||
variant: canEdit ? ButtonVariant.secondary : ButtonVariant.secondary,
|
||
onPressed: canEdit ? () {} : null,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _MedicationsSection extends StatelessWidget {
|
||
final List<DataItem> medications;
|
||
|
||
const _MedicationsSection({required this.medications});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'MEDICATIONS',
|
||
indicatorColor: InouTheme.indicatorMedications,
|
||
summary: '${medications.length} medications',
|
||
child: Column(
|
||
children: [
|
||
for (final med in medications) _DataRow(item: med),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _SymptomsSection extends StatelessWidget {
|
||
final List<DataItem> symptoms;
|
||
|
||
const _SymptomsSection({required this.symptoms});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'SYMPTOMS',
|
||
indicatorColor: const Color(0xFFF59E0B),
|
||
summary: '${symptoms.length} symptoms',
|
||
child: Column(
|
||
children: [
|
||
for (final symptom in symptoms) _DataRow(item: symptom),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _HospitalizationsSection extends StatelessWidget {
|
||
final List<DataItem> hospitalizations;
|
||
|
||
const _HospitalizationsSection({required this.hospitalizations});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'HOSPITALIZATIONS',
|
||
indicatorColor: const Color(0xFFEF4444),
|
||
summary: '${hospitalizations.length} hospitalizations',
|
||
child: Column(
|
||
children: [
|
||
for (final hosp in hospitalizations) _DataRow(item: hosp),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _TherapiesSection extends StatelessWidget {
|
||
final List<DataItem> therapies;
|
||
|
||
const _TherapiesSection({required this.therapies});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'THERAPIES',
|
||
indicatorColor: const Color(0xFF10B981),
|
||
summary: '${therapies.length} therapies',
|
||
child: Column(
|
||
children: [
|
||
for (final therapy in therapies) _DataRow(item: therapy),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _VitalsSection extends StatelessWidget {
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'VITALS',
|
||
indicatorColor: InouTheme.indicatorVitals,
|
||
summary: 'Track blood pressure, weight, temperature, and more',
|
||
trailing: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: InouTheme.bg,
|
||
border: Border.all(color: InouTheme.border),
|
||
borderRadius: BorderRadius.circular(4),
|
||
),
|
||
child: Text(
|
||
'COMING SOON',
|
||
style: InouText.labelCaps.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
),
|
||
comingSoon: true,
|
||
);
|
||
}
|
||
}
|
||
|
||
class _PrivacySection extends StatelessWidget {
|
||
final List<AccessEntry> accessList;
|
||
final String dossierId;
|
||
final bool canManageAccess;
|
||
|
||
const _PrivacySection({
|
||
required this.accessList,
|
||
required this.dossierId,
|
||
required this.canManageAccess,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return _DataCard(
|
||
title: 'PRIVACY',
|
||
indicatorColor: InouTheme.indicatorPrivacy,
|
||
summary: '${accessList.length} people with access',
|
||
child: Column(
|
||
children: [
|
||
for (final access in accessList)
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'${access.name}${access.isSelf ? ' (you)' : ''}${access.isPending ? ' (pending)' : ''}',
|
||
style: InouText.body.copyWith(fontWeight: FontWeight.w500),
|
||
),
|
||
Text(
|
||
'${access.relation}${access.canEdit ? ' · can edit' : ''}',
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (canManageAccess && !access.isSelf) ...[
|
||
InouButton(
|
||
text: 'Edit',
|
||
size: ButtonSize.small,
|
||
variant: ButtonVariant.secondary,
|
||
onPressed: () {},
|
||
),
|
||
const SizedBox(width: 8),
|
||
InouButton(
|
||
text: 'Remove',
|
||
size: ButtonSize.small,
|
||
variant: ButtonVariant.danger,
|
||
onPressed: () {},
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
// Privacy actions row
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
decoration: BoxDecoration(
|
||
color: InouTheme.bg,
|
||
border: Border(top: BorderSide(color: InouTheme.border)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
_PrivacyAction(text: 'Share access', onTap: () {}),
|
||
const SizedBox(width: 24),
|
||
if (canManageAccess) ...[
|
||
_PrivacyAction(text: 'Manage permissions', onTap: () {}),
|
||
const SizedBox(width: 24),
|
||
],
|
||
_PrivacyAction(text: 'View audit log', onTap: () {}),
|
||
const SizedBox(width: 24),
|
||
_PrivacyAction(text: 'Export data', onTap: () {}),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _PrivacyAction extends StatelessWidget {
|
||
final String text;
|
||
final VoidCallback onTap;
|
||
|
||
const _PrivacyAction({required this.text, required this.onTap});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
child: Text(
|
||
text,
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.accent),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// SHARED WIDGETS
|
||
// ============================================
|
||
|
||
class _DataCard extends StatelessWidget {
|
||
final String title;
|
||
final Color indicatorColor;
|
||
final String summary;
|
||
final Widget? trailing;
|
||
final Widget? child;
|
||
final bool comingSoon;
|
||
|
||
const _DataCard({
|
||
required this.title,
|
||
required this.indicatorColor,
|
||
required this.summary,
|
||
this.trailing,
|
||
this.child,
|
||
this.comingSoon = false,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
margin: const EdgeInsets.only(bottom: 16),
|
||
decoration: BoxDecoration(
|
||
color: InouTheme.bgCard,
|
||
border: Border.all(color: InouTheme.border),
|
||
borderRadius: InouTheme.borderRadiusLg,
|
||
),
|
||
child: Opacity(
|
||
opacity: comingSoon ? 0.6 : 1.0,
|
||
child: Column(
|
||
children: [
|
||
// Header
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
// Indicator bar
|
||
Container(
|
||
width: 4,
|
||
height: 32,
|
||
decoration: BoxDecoration(
|
||
color: indicatorColor,
|
||
borderRadius: BorderRadius.circular(2),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
// Title and summary
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: InouText.labelCaps.copyWith(
|
||
letterSpacing: 0.8,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
summary,
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (trailing != null) trailing!,
|
||
],
|
||
),
|
||
),
|
||
// Content
|
||
if (child != null)
|
||
Container(
|
||
decoration: BoxDecoration(
|
||
border: Border(top: BorderSide(color: InouTheme.border)),
|
||
),
|
||
child: child,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DataRow extends StatelessWidget {
|
||
final DataItem item;
|
||
final bool showType;
|
||
|
||
const _DataRow({required this.item, this.showType = false});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
decoration: BoxDecoration(
|
||
border: Border(bottom: BorderSide(color: InouTheme.border, style: BorderStyle.solid)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
item.value,
|
||
style: InouText.body.copyWith(fontWeight: FontWeight.w500),
|
||
),
|
||
if (item.summary != null)
|
||
Text(
|
||
item.summary!,
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (showType && item.type != null) ...[
|
||
Text(
|
||
item.type!,
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
const SizedBox(width: 16),
|
||
],
|
||
if (item.date != null)
|
||
Text(
|
||
item.date!,
|
||
style: InouText.mono.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _ShowMoreRow extends StatelessWidget {
|
||
final String text;
|
||
final VoidCallback onTap;
|
||
|
||
const _ShowMoreRow({required this.text, required this.onTap});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
border: Border(top: BorderSide(color: InouTheme.border)),
|
||
),
|
||
child: Text(
|
||
text,
|
||
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|