Layout changes, fix bugs in new entry page
This commit is contained in:
@@ -26,6 +26,55 @@ class _TripsPageState extends State<TripsPage> {
|
||||
await context.read<DataService>().fetchTripDetails();
|
||||
}
|
||||
|
||||
Future<void> _renameTrip(TripDetail trip, String newName) async {
|
||||
final data = context.read<DataService>();
|
||||
final api = data.api;
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
try {
|
||||
await api.post('/trips/rename', {
|
||||
"trip_id": trip.id,
|
||||
"trip_name": newName,
|
||||
});
|
||||
await Future.wait([
|
||||
data.fetchTripDetails(),
|
||||
data.fetchTrips(),
|
||||
]);
|
||||
} catch (e) {
|
||||
messenger?.showSnackBar(
|
||||
SnackBar(content: Text('Failed to rename trip: $e')),
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _promptTripName(BuildContext context, String initial) async {
|
||||
final controller = TextEditingController(text: initial);
|
||||
final newName = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (dialogCtx) => AlertDialog(
|
||||
title: const Text('Rename trip'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: const InputDecoration(labelText: 'Trip name'),
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogCtx).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(dialogCtx).pop(controller.text.trim()),
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
controller.dispose();
|
||||
return newName;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = context.watch<DataService>();
|
||||
@@ -218,52 +267,85 @@ class _TripsPageState extends State<TripsPage> {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (_) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
bool renaming = false;
|
||||
String tripName = trip.name;
|
||||
return StatefulBuilder(
|
||||
builder: (sheetCtx, setSheetState) {
|
||||
Future<void> handleRename() async {
|
||||
final newName =
|
||||
await _promptTripName(sheetCtx, tripName) ?? tripName;
|
||||
if (newName.isEmpty || newName == tripName) return;
|
||||
setSheetState(() => renaming = true);
|
||||
try {
|
||||
await _renameTrip(trip, newName);
|
||||
tripName = newName;
|
||||
setSheetState(() {});
|
||||
} finally {
|
||||
if (mounted) setSheetState(() => renaming = false);
|
||||
}
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(sheetCtx).pop(),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
tripName,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: renaming
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.edit),
|
||||
tooltip: 'Rename trip',
|
||||
onPressed: renaming ? null : handleRename,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text('${trip.mileage.toStringAsFixed(1)} mi'),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
trip.name,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
child: ListView.builder(
|
||||
itemCount: trip.legs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final leg = trip.legs[index];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.train),
|
||||
title: Text('${leg.start} → ${leg.end}'),
|
||||
subtitle: Text(_formatDate(leg.beginTime)),
|
||||
trailing: Text(
|
||||
leg.mileage?.toStringAsFixed(1) ?? '-',
|
||||
style: Theme.of(context).textTheme.labelLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text('${trip.mileage.toStringAsFixed(1)} mi'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
child: ListView.builder(
|
||||
itemCount: trip.legs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final leg = trip.legs[index];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.train),
|
||||
title: Text('${leg.start} → ${leg.end}'),
|
||||
subtitle: Text(_formatDate(leg.beginTime)),
|
||||
trailing: Text(
|
||||
leg.mileage?.toStringAsFixed(1) ?? '-',
|
||||
style: Theme.of(context).textTheme.labelLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user