Initial Commit
This commit is contained in:
393
lib/pages/home.dart
Normal file
393
lib/pages/home.dart
Normal file
@@ -0,0 +1,393 @@
|
||||
|
||||
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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user