smart_seat_selector 0.1.0
smart_seat_selector: ^0.1.0 copied to clipboard
A universal grid-based seat selection widget for Flutter supporting Cinemas, Buses, Flights, and Event halls with zoom and pan interaction.
Smart Seat Selector #
A universal grid-based seat selection widget for Flutter β supporting Cinemas, Buses, Flights, and Event venues with multi-seat types, per-type pricing, zoom & pan, animated selection, tooltips, and a live summary bar.
Features #
| π Multi-seat types | Economy, Business, VIP, Wheelchair β each with its own color, icon, shape, and price |
| π° Live pricing | totalPrice getter updates in real time as seats are selected |
| π¨ Per-type styling | Rounded-rect, circle, or stadium shape; custom icon per type |
| π¬ Rich tooltips | Long-press any seat to see its label, type, status, and price |
| β¨ Animations | Scale bounce on selection + ink ripple on tap |
| π·οΈ Row & column headers | Auto-aligned AβZ row labels and 1βN column numbers |
| π Summary bar | Sticky bottom bar with selected seats, labels, total price, and confirm button |
| πΊοΈ Legend widget | Auto-generated color legend that always matches your config |
| π Zoom & pan | Built-in InteractiveViewer for large seat maps |
| π§ Smart controller | selectRow(), selectByType(), selectAll(), clearSelection() |
| π Safe state | Deep-copied immutable grid β external mutations can't corrupt state |
Installation #
dependencies:
smart_seat_selector: ^0.1.0
import 'package:smart_seat_selector/smart_seat_selector.dart';
Quick start #
1 β Define your grid #
Use the Seat factory helpers to build a TypedSeatGrid:
final TypedSeatGrid grid = [
// Row A β VIP front row
[Seat.vip(), Seat.vip(), Seat.gap(), Seat.gap(), Seat.vip(), Seat.vip()],
// Row B β Business
[Seat.business(), Seat.business(booked: true), Seat.gap(), Seat.gap(), Seat.business(), Seat.business()],
// Row C β Economy
[Seat.economy(), Seat.economy(), Seat.gap(), Seat.gap(), Seat.economy(), Seat.economy()],
// Row D β Economy + accessible bays
[Seat.wheelchair(), Seat.economy(), Seat.gap(), Seat.gap(), Seat.economy(), Seat.wheelchair()],
];
Every cell is a SeatMeta β combining a SeatState (available / booked / disabled / gap) with a SeatType (economy / business / vip / wheelchair).
2 β Create a controller #
late SeatController _controller;
@override
void initState() {
super.initState();
_controller = SeatController(
seatGrid: grid,
maxSelection: 4,
prices: const {
SeatType.economy: 10.0,
SeatType.business: 25.0,
SeatType.vip: 60.0,
SeatType.wheelchair: 0.0,
},
);
}
@override
void dispose() {
_controller.dispose(); // always dispose
super.dispose();
}
3 β Render the layout #
SeatLayout(
controller: _controller,
seatConfig: const SeatLayoutConfig(
seatSize: 40,
showRowLabels: true,
showColLabels: true,
showTooltip: true,
enableAnimations: true,
),
onSeatStateChanged: (List<SeatPoint> selected) {
print('Selected: $selected');
print('Total: \$${_controller.totalPriceFormatted}');
},
)
4 β Add a legend and summary bar #
Column(
children: [
Expanded(
child: SeatLayout(controller: _controller, seatConfig: config),
),
SeatLegend(config: config), // color legend
SeatSummaryBar( // live bottom bar
controller: _controller,
config: config,
confirmLabel: 'Book Tickets',
onConfirm: (seats) => print('Booking $seats'),
),
],
)
Seat types #
Four built-in types, each with a ready-to-use preset:
| Type | Preset | Shape | Icon | Default price |
|---|---|---|---|---|
SeatType.economy |
SeatTypeConfig.defaultEconomy |
Rounded rect | β | free |
SeatType.business |
SeatTypeConfig.defaultBusiness |
Rounded rect | star_border_rounded |
$25 |
SeatType.vip |
SeatTypeConfig.defaultVip |
Stadium (pill) | workspace_premium_rounded |
$60 |
SeatType.wheelchair |
SeatTypeConfig.defaultWheelchair |
Circle | accessible_rounded |
free |
Custom type config #
Override any preset or create your own:
SeatLayoutConfig(
typeConfigs: {
SeatType.economy: const SeatTypeConfig(
label: 'Standard',
price: 8.0,
availableColor: Color(0xFFE8EAF6),
selectedColor: Color(0xFF3949AB),
selectionBorderColor: Color(0xFF1A237E),
),
SeatType.vip: const SeatTypeConfig(
label: 'Premium',
price: 45.0,
shape: SeatShape.stadium,
icon: Icons.star_rounded,
availableColor: Color(0xFFFFF8E1),
selectedColor: Color(0xFFF57F17),
selectionBorderColor: Color(0xFFE65100),
),
},
)
Migrating from v0.0.x #
If you have an existing integer grid, use the fromInts factory β no grid rewrite needed:
// Old code
controller = SeatController(seatGrid: myIntGrid, maxSelection: 3);
// New code β drop-in replacement
controller = SeatController.fromInts(seatGrid: myIntGrid, maxSelection: 3);
All seats default to SeatType.economy. You can then migrate rows to typed grids incrementally.
API reference #
SeatController #
| Member | Description |
|---|---|
SeatController({seatGrid, maxSelection, prices, initialSelection}) |
Main constructor β accepts TypedSeatGrid |
SeatController.fromInts({seatGrid, ...}) |
Legacy factory β accepts List<List<int>> |
selectedSeats β Set<SeatPoint> |
Currently selected seats (unmodifiable) |
selectedCount β int |
Number of selected seats |
isMaxReached β bool |
Whether maxSelection has been hit |
totalPrice β double |
Sum of prices for selected seats |
totalPriceFormatted β String |
e.g. "42.00" |
getState(row, col) β SeatState |
Current state (selection overrides stored state) |
getType(row, col) β SeatType |
Seat class at that cell |
getSeatMeta(row, col) β SeatMeta |
Full metadata for a cell |
toggleSeat(row, col) |
Select / deselect a seat |
selectSeats(List<SeatPoint>) |
Bulk select by coordinate |
selectRow(int row) |
Select all available seats in a row |
selectByType(SeatType) |
Select all available seats of a given type |
selectAll() |
Select all available seats up to maxSelection |
clearSelection() |
Deselect everything |
dispose() |
Always call in your State.dispose() |
SeatLayoutConfig #
| Property | Type | Default | Description |
|---|---|---|---|
seatSize |
double |
40.0 |
Width & height of each seat |
gapSize |
double |
10.0 |
Spacing between seats |
typeConfigs |
Map<SeatType, SeatTypeConfig> |
All 4 presets | Per-type visual & pricing config |
showRowLabels |
bool |
false |
A, B, C⦠labels on the left |
showColLabels |
bool |
false |
1, 2, 3β¦ numbers on the top |
enableAnimations |
bool |
true |
Scale bounce + ripple |
showTooltip |
bool |
true |
Long-press tooltip |
availableColor |
Color |
#E0E0E0 |
Fallback available color |
bookedColor |
Color |
#BDBDBD |
Fallback booked color |
selectedColor |
Color |
#4CAF50 |
Fallback selected color |
disabledColor |
Color |
#EEEEEE |
Disabled seat color |
selectionBorderColor |
Color |
#2E7D32 |
Fallback selection border |
borderRadius |
BorderRadius |
8.0 |
Corner radius for rounded-rect seats |
labelStyle |
TextStyle? |
null |
Override seat label text style |
SeatTypeConfig #
| Property | Type | Description |
|---|---|---|
label |
String |
Display name in legend and tooltip |
price |
double |
Per-seat price (used by totalPrice) |
currencySymbol |
String |
Shown in tooltip, default $ |
availableColor |
Color? |
Override for available state |
bookedColor |
Color? |
Override for booked state |
selectedColor |
Color? |
Override for selected state |
selectionBorderColor |
Color? |
Override for selection border |
shape |
SeatShape |
roundedRect / circle / stadium |
icon |
IconData? |
Icon drawn inside the seat |
iconOnly |
bool |
Hide label, show only icon |
Seat factory helpers #
Seat.gap() // aisle cell
Seat.economy() // available economy
Seat.economy(booked: true) // booked economy
Seat.business()
Seat.vip()
Seat.wheelchair()
Seat.available(type: SeatType.vip, id: 'VIP-3') // with backend id
Seat.gridFromInts(myIntGrid) // convert legacy int grid
Widgets #
| Widget | Description |
|---|---|
SeatLayout |
Main grid widget β scrollable, zoomable, animated |
SeatLegend |
Color swatch row matching your SeatLayoutConfig |
SeatSummaryBar |
Sticky bottom bar with count, labels, price, confirm button |
License #
MIT β see LICENSE.