Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active May 5, 2024 15:25
Show Gist options
  • Save PlugFox/218f5884cf61130433cd684783fd58b7 to your computer and use it in GitHub Desktop.
Save PlugFox/218f5884cf61130433cd684783fd58b7 to your computer and use it in GitHub Desktop.
Seat Viewer
/*
* Seat Viewer
* https://gist.github.com/PlugFox/218f5884cf61130433cd684783fd58b7
* https://dartpad.dev?id=218f5884cf61130433cd684783fd58b7
* Mike Matiunin <plugfox@gmail.com>, 05 May 2024
*/
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show listEquals;
import 'package:flutter/material.dart';
void main() => runApp(const MainApp());
typedef Seat = ({int number, bool reserved});
final List<Seat> seats = () {
final rnd = math.Random();
return <Seat>[
for (int i = 1; i <= 225; i++)
(
number: i,
reserved: rnd.nextInt(5) == 0,
),
];
}();
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Seats Viewer',
darkTheme: ThemeData.light(),
home: Scaffold(
body: SeatsViewer(
seats: seats,
),
),
);
}
class SeatsViewer extends StatefulWidget {
const SeatsViewer({
required this.seats,
this.size = const Size.square(64),
this.margin = const EdgeInsets.all(2),
this.seatsPerRow,
super.key,
});
final List<Seat> seats;
final int? seatsPerRow;
final Size size;
final EdgeInsets margin;
@override
State<SeatsViewer> createState() => _SeatsViewerState();
}
class _SeatsViewerState extends State<SeatsViewer> {
final controller = TransformationController();
late List<Widget> positions;
@override
void initState() {
super.initState();
_rebuild();
}
@override
void didUpdateWidget(covariant SeatsViewer oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.size == widget.size &&
oldWidget.margin == widget.margin &&
listEquals(oldWidget.seats, widget.seats)) return;
_rebuild();
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
void _rebuild() {
positions = widget.seats
.map(
(seat) => Padding(
padding: widget.margin,
child: SizedBox.fromSize(
size: widget.size,
child: Opacity(
opacity: seat.reserved ? 0.5 : 1.0,
child: Material(
color: seat.reserved ? Colors.red : Colors.green,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
elevation: 4,
child: InkWell(
onTap: seat.reserved ? null : () {},
borderRadius: BorderRadius.circular(16),
child: Center(
child: Text(
seat.number.toString(),
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
),
)
.toList(growable: false);
}
@override
Widget build(BuildContext context) => InteractiveViewer(
constrained: true,
transformationController: controller,
minScale: 0.8,
maxScale: 1.6,
boundaryMargin: const EdgeInsets.all(64),
child: Flow(
clipBehavior: Clip.none,
delegate: _SeatsFlowDelegate(
seatsPerRow: widget.seatsPerRow,
),
children: positions,
),
);
}
class _SeatsFlowDelegate extends FlowDelegate {
const _SeatsFlowDelegate({
this.seatsPerRow,
});
final int? seatsPerRow;
@override
void paintChildren(FlowPaintingContext context) {
final count = context.childCount;
if (count == 0) return;
final layoutSize = context.size;
final seatSize = context.getChildSize(0)!;
final seatsPerRow = this.seatsPerRow ?? math.sqrt(seats.length).ceil();
final scaleX = layoutSize.width / (seatSize.width * seatsPerRow);
final scaleY =
layoutSize.height / (seatSize.height * (count / seatsPerRow).ceil());
final scale = math.min(scaleX, scaleY);
for (var i = 0; i < count; i++) {
final x = (i % seatsPerRow) * seatSize.width;
final y = (i ~/ seatsPerRow) * seatSize.height;
context.paintChild(
i,
transform: Matrix4.identity()
..scale(scale, scale, 1.0)
..translate(x, y, 0),
);
}
}
@override
bool shouldRepaint(covariant _SeatsFlowDelegate oldDelegate) =>
oldDelegate.seatsPerRow != seatsPerRow;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment