Files
2025-12-27 18:24:05 +00:00

393 lines
9.7 KiB
Dart

import 'dart:ui';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:proto_portal/scraper.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import '../widgets/faded_scroll_view.dart';
import '../widgets/service_card.dart';
class StationPage extends StatefulWidget {
static GoRoute route = GoRoute(
path: "/station",
builder: (context, state) => StationPage(stationName: state.uri.queryParameters["station"],)
);
String? stationName;
StationPage({
this.stationName
});
@override
State<StationPage> createState() => _StationPageState();
}
class _StationPageState extends State<StationPage> {
GtiStation? _station = null;
List<GtiService>? services = null;
@override
void initState() {
// TODO: implement initState
super.initState();
if (widget.stationName != null) {
setStation(widget.stationName!);
}
}
Future<void> setStation(String stationName) async {
if (true) {
List<GtiStation> results = await GtiAuth.of(context).searchStations(stationName.trim());
if (results.isNotEmpty) {
setState(() {
_station = results.first;
widget.stationName = _station!.label;
});
} else {
setState(() {
widget.stationName = null;
_station = null;
});
return;
}
// Get Services.
List<GtiService> fetchedServices = await GtiAuth.of(context).getServicesForStation(_station!);
setState(() {
services = fetchedServices;
});
}
}
Future<void> selectStation(GtiStation station) async {
setState(() {
_station = station;
widget.stationName = station.label;
});
// Get Services.
List<GtiService> fetchedServices = await GtiAuth.of(context).getServicesForStation(station);
setState(() {
services = fetchedServices;
});
}
@override
Widget build(BuildContext context) {
double topPadding = MediaQuery.of(context).padding.top;
double bottomPadding = MediaQuery.of(context).padding.bottom;
print("Built page page");
if (_station == null) {
return StationSearch(
onSelected: (station) {
// GoRouter.of(context).go(
// "${StationPage.route.path}?station=${station.code}"
// );
selectStation(station);
print("Selected station: ${station.label}");
},
);
}
return _scaffoldBgAndTing(
context: context,
child: Column(
children: [
SizedBox(height: topPadding),
Column(
children: [
// Container(
// height: 40,
// width: double.infinity,
// alignment: Alignment.center,
// child: SvgPicture.asset(
// "assets/logo.svg",
// alignment: AlignmentGeometry.center,
// width: 250,
// colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
// ),
// ),
Container(
height: 40,
alignment: Alignment.center,
child: Text(
_station?.label ?? "Unknown Station"
).h4,
)
],
),
const SizedBox(height: 4,),
// Content
Expanded(
child: services == null
? Center(child: CircularProgressIndicator())
: services!.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(LucideIcons.trainTrack, size: 48),
SizedBox(height: 16),
Text("No services available").muted,
],
),
)
: FadedScrollView(
padding: const EdgeInsets.all(4.0),
fadeHeight: 20,
easingDistance: 5,
child: Padding(
padding: EdgeInsets.zero,
child: Column(
spacing: 8,
children: [
for (final service in services!)
ServiceCard.fromService(
service: service,
station: _station,
showActions: true,
).withMargin(horizontal: 4),
],
),
),
),
),
NavigationBar(
padding: EdgeInsets.only(
bottom: bottomPadding
),
children: [
NavigationItem(
child: Icon(
LucideIcons.house
),
),
NavigationItem(
child: Icon(
LucideIcons.house
),
),
NavigationItem(
child: Icon(
LucideIcons.house
),
),
NavigationItem(
child: Icon(
LucideIcons.house
),
)
],
)
]
)
);
}
}
class StationSearch extends StatefulWidget{
Function(GtiStation) onSelected;
StationSearch({
required this.onSelected
});
@override
State<StationSearch> createState() => _StationSearchState();
}
class _StationSearchState extends State<StationSearch> {
TextEditingController _controller = TextEditingController();
List<GtiStation> _results = [];
@override
Widget build(BuildContext context) {
double topPadding = MediaQuery.of(context).padding.top;
double bottomPadding = MediaQuery.of(context).padding.bottom;
return _scaffoldBgAndTing(
context: context,
child: Column(
children: [
SizedBox(height: topPadding),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
if (_results.isEmpty)
Text(
"No results"
).bold.large.withPadding(all: 16)
else for (final GtiStation station in _results.sublist(0, _results.length-1)) ...[
_stationButton(station),
Divider()
],
if (_results.isNotEmpty)
_stationButton(_results.last)
],
),
)
),
Divider(
color: Theme.of(context).colorScheme.border,
),
OutlinedContainer(
borderRadius: BorderRadius.zero,
borderWidth: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Type station name"
).small.semiBold,
const SizedBox(height: 10,),
TextField(
controller: _controller,
placeholder: Text(
"e.g. Redhill"
),
onChanged: (value) async {
List<GtiStation> results = await GtiAuth.of(context).searchStations(value);
setState(() {
_results = results;
});
}
),
SizedBox(
height: bottomPadding,
)
],
).withPadding(all: 16),
)
],
)
);
}
Widget _stationButton(GtiStation station) {
return Button.ghost(
onPressed: () {
widget.onSelected(station);
},
alignment: Alignment.centerLeft,
child: Basic(
title: Text(
station.label
).h4,
),
).sized(width: double.infinity).withPadding(all: 8);
}
}
Widget _scaffoldBgAndTing({
required BuildContext context,
required Widget child,
}) {
// light/dark mode color
Color bgColor = Colors.black;
if (Theme.of(context).brightness == Brightness.light) {
bgColor = Colors.white;
} else {
bgColor = Colors.black;
}
return Scaffold(
child: Stack(
children: [
// Image goes BEHIND the BackdropFilter
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/background-1.jpeg"),
fit: BoxFit.cover,
),
),
),
// BackdropFilter blurs the image above
BackdropFilter(
filter: ImageFilter.blur(
sigmaY: 10.0,
sigmaX: 10.0,
),
child: Container(
color: bgColor.withOpacity(0.3), // Or add a tint if desired
),
),
Positioned.fill(
child: Column(
children: [
Container(
height: 120,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.8),
Colors.black.withOpacity(0.5),
Colors.transparent,
],
),
),
)
],
),
),
child,
],
),
);
}