393 lines
9.7 KiB
Dart
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,
|
|
],
|
|
),
|
|
);
|
|
} |