Initial Commit

This commit is contained in:
ImBenji
2025-12-27 18:24:05 +00:00
commit 21d95e7a9f
142 changed files with 8062 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class FadedScrollView extends StatefulWidget {
final Widget child;
final double fadeHeight;
final double easingDistance;
final double maxBlur;
final ScrollController? controller;
final EdgeInsetsGeometry? padding;
const FadedScrollView({
super.key,
required this.child,
this.fadeHeight = 20.0,
this.easingDistance = 10.0,
this.maxBlur = 3.0,
this.controller,
this.padding,
});
@override
State<FadedScrollView> createState() => _FadedScrollViewState();
}
class _FadedScrollViewState extends State<FadedScrollView> {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = widget.controller ?? ScrollController();
}
@override
void dispose() {
if (widget.controller == null) {
_scrollController.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _scrollController,
builder: (context, child) {
final double offset = _scrollController.hasClients ? _scrollController.offset : 0.0;
final double fadeProgress = (offset / widget.easingDistance).clamp(0.0, 1.0);
final double currentBlur = widget.maxBlur * fadeProgress;
return Stack(
children: [
// Main content
ShaderMask(
shaderCallback: (Rect bounds) {
final double fadeStop = widget.fadeHeight / bounds.height;
final Color transparentColor = Colors.white.withOpacity(1.0 - fadeProgress);
return LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
transparentColor,
Colors.white,
Colors.white,
],
stops: [0.0, fadeStop, 1.0],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: SingleChildScrollView(
controller: _scrollController,
padding: widget.padding,
child: widget.child,
),
),
// Single blur with extended fade area for smoother transition
if (currentBlur > 0)
Positioned(
top: 0,
left: 0,
right: 0,
height: widget.fadeHeight * 1.5, // Extend beyond fade area
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: currentBlur * 0.7, // Reduce intensity slightly
sigmaY: currentBlur * 0.7,
),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.transparent,
Colors.transparent,
Colors.black.withOpacity(0.0),
],
stops: [0.0, 0.6, 0.9, 1.0],
),
),
),
),
),
),
],
);
},
);
}
}

View File

@@ -0,0 +1,219 @@
import 'dart:convert';
import 'package:flutter/material.dart' hide Colors, Divider, VerticalDivider;
import 'package:go_router/go_router.dart';
import 'package:proto_portal/scraper.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
class ServiceCard extends StatelessWidget {
final String type;
final String time;
final String diagram;
final String customer;
final String destination;
final String operator;
final String vrm;
final GtiService? service;
final GtiStation? station;
final bool showActions;
// Constructor for individual fields
ServiceCard({
required this.type,
required this.time,
required this.diagram,
required this.customer,
required this.destination,
required this.operator,
required this.vrm,
this.service,
this.station,
this.showActions = false,
});
// Constructor that takes a GtiService
ServiceCard.fromService({
required GtiService service,
GtiStation? station,
bool showActions = false,
}) : this.service = service,
this.station = station,
this.showActions = showActions,
type = service.type,
time = service.time,
diagram = service.coach,
customer = service.customer,
destination = service.destination,
operator = service.operator,
vrm = service.vrm;
@override
Widget build(BuildContext context) {
// Convert type code to full word
String typeLabel = type;
if (type == 'D') {
typeLabel = 'Departure';
} else if (type == 'A') {
typeLabel = 'Arrival';
}
return OutlinedContainer(
width: double.infinity,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
spreadRadius: 2)
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IntrinsicHeight(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(typeLabel,
style: TextStyle(fontSize: 22))
.textLarge,
],
),
),
),
VerticalDivider(),
SizedBox(
width: 94,
child: _keyvalueRow(
key: "Time",
value: time,
),
),
VerticalDivider(),
SizedBox(
width: 130,
child: _keyvalueRow(
key: "Diagram",
value: diagram,
),
),
],
),
),
Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: _keyvalueRow(
key: "Customer",
value: customer,
),
),
Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: _keyvalueRow(
key: "Destination",
value: destination,
),
),
Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: IntrinsicHeight(
child: Row(
children: [
Expanded(
child: _keyvalueRow(
key: "Operator",
value: operator,
),
),
VerticalDivider(),
SizedBox(
width: 120,
child: _keyvalueRow(
key: "VRM",
value: vrm.isNotEmpty ? vrm : "N/A",
),
)
],
),
),
),
if (showActions) ...[
Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ButtonGroup(
children: [
Expanded(
child: OutlineButton(
onPressed: service != null && station != null
? () {
// Encode service as base64
final serviceJson = jsonEncode(service!.toJson());
final serviceBase64 = base64Encode(utf8.encode(serviceJson));
final stationJson = jsonEncode({
'label': station!.label,
'crs': station!.crs,
});
final stationBase64 = base64Encode(utf8.encode(stationJson));
GoRouter.of(context).push(
Uri(
path: '/audit',
queryParameters: {
'service': serviceBase64,
'station': stationBase64,
},
).toString(),
);
}
: null,
child: Text("Audit"),
),
),
Expanded(
child: PrimaryButton(
onPressed: () {},
child: Text("Depart"),
),
)
],
)
],
),
),
],
],
),
);
}
Widget _keyvalueRow({
required String key,
required String value,
}) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(key).h4,
Text(value).small
],
),
);
}
}