247 lines
7.8 KiB
Dart
247 lines
7.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:mileograph_flutter/components/legs/leg_card.dart';
|
|
import 'package:mileograph_flutter/services/data_service.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class LegsPage extends StatefulWidget {
|
|
const LegsPage({super.key});
|
|
|
|
@override
|
|
State<LegsPage> createState() => _LegsPageState();
|
|
}
|
|
|
|
class _LegsPageState extends State<LegsPage> {
|
|
int _sortDirection = 0;
|
|
DateTime? _startDate;
|
|
DateTime? _endDate;
|
|
bool _initialised = false;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (!_initialised) {
|
|
_initialised = true;
|
|
_refreshLegs();
|
|
}
|
|
}
|
|
|
|
Future<void> _refreshLegs() async {
|
|
final data = context.read<DataService>();
|
|
await data.fetchLegs(
|
|
sortDirection: _sortDirection,
|
|
dateRangeStart: _formatDate(_startDate),
|
|
dateRangeEnd: _formatDate(_endDate),
|
|
);
|
|
}
|
|
|
|
Future<void> _loadMore() async {
|
|
final data = context.read<DataService>();
|
|
await data.fetchLegs(
|
|
sortDirection: _sortDirection,
|
|
dateRangeStart: _formatDate(_startDate),
|
|
dateRangeEnd: _formatDate(_endDate),
|
|
offset: data.legs.length,
|
|
append: true,
|
|
);
|
|
}
|
|
|
|
double _pageMileage(List legs) {
|
|
return legs.fold<double>(
|
|
0,
|
|
(prev, leg) => prev + (leg.mileage as double? ?? 0),
|
|
);
|
|
}
|
|
|
|
Future<void> _pickDate({required bool start}) async {
|
|
final initial = start
|
|
? _startDate ?? DateTime.now()
|
|
: _endDate ?? _startDate ?? DateTime.now();
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: initial,
|
|
firstDate: DateTime(1970),
|
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
|
);
|
|
if (picked != null) {
|
|
setState(() {
|
|
if (start) {
|
|
_startDate = picked;
|
|
if (_endDate != null && _endDate!.isBefore(picked)) {
|
|
_endDate = picked;
|
|
}
|
|
} else {
|
|
_endDate = picked;
|
|
}
|
|
});
|
|
await _refreshLegs();
|
|
}
|
|
}
|
|
|
|
void _clearFilters() {
|
|
setState(() {
|
|
_startDate = null;
|
|
_endDate = null;
|
|
_sortDirection = 0;
|
|
});
|
|
_refreshLegs();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final data = context.watch<DataService>();
|
|
final legs = data.legs;
|
|
final pageMileage = _pageMileage(legs);
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: _refreshLegs,
|
|
child: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Logbook',
|
|
style: Theme.of(context).textTheme.labelMedium),
|
|
const SizedBox(height: 2),
|
|
Text('Entries',
|
|
style: Theme.of(context).textTheme.headlineSmall),
|
|
],
|
|
),
|
|
Card(
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text('Page mileage',
|
|
style: Theme.of(context).textTheme.labelSmall),
|
|
Text('${pageMileage.toStringAsFixed(1)} mi',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('Filters',
|
|
style: Theme.of(context).textTheme.titleMedium),
|
|
TextButton.icon(
|
|
onPressed: _clearFilters,
|
|
icon: const Icon(Icons.refresh),
|
|
label: const Text('Clear'),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 12,
|
|
runSpacing: 12,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: [
|
|
FilledButton.tonalIcon(
|
|
onPressed: () => _pickDate(start: true),
|
|
icon: const Icon(Icons.calendar_month),
|
|
label: Text(
|
|
_startDate == null
|
|
? 'Start date'
|
|
: _formatDate(_startDate!)!,
|
|
),
|
|
),
|
|
FilledButton.tonalIcon(
|
|
onPressed: () => _pickDate(start: false),
|
|
icon: const Icon(Icons.event),
|
|
label: Text(
|
|
_endDate == null
|
|
? 'End date'
|
|
: _formatDate(_endDate!)!,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
if (data.isLegsLoading && legs.isEmpty)
|
|
const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 24.0),
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
else if (legs.isEmpty)
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'No entries found',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text('Adjust the filters or add a new leg.'),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
else
|
|
Column(
|
|
children: [
|
|
...legs.map((leg) => LegCard(leg: leg)),
|
|
const SizedBox(height: 8),
|
|
if (data.legsHasMore || data.isLegsLoading)
|
|
Align(
|
|
alignment: Alignment.center,
|
|
child: OutlinedButton.icon(
|
|
onPressed:
|
|
data.isLegsLoading ? null : () => _loadMore(),
|
|
icon: data.isLegsLoading
|
|
? const SizedBox(
|
|
height: 14,
|
|
width: 14,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.expand_more),
|
|
label: Text(
|
|
data.isLegsLoading ? 'Loading...' : 'Load more',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String? _formatDate(DateTime? date) {
|
|
if (date == null) return null;
|
|
return '${date.year.toString().padLeft(4, '0')}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
}
|