venue_seat_picker 0.1.1 copy "venue_seat_picker: ^0.1.1" to clipboard
venue_seat_picker: ^0.1.1 copied to clipboard

Interactive, editable venue seat layouts for Flutter, built for and used in production by Festapp.

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:venue_seat_picker/venue_seat_picker.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Venue seat picker',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
    darkTheme: ThemeData(
      colorScheme: ColorScheme.fromSeed(
        seedColor: Colors.indigo,
        brightness: Brightness.dark,
      ),
      useMaterial3: true,
    ),
    themeMode: ThemeMode.system,
    home: const ExamplePage(),
  );
}

class ExamplePage extends StatefulWidget {
  const ExamplePage({super.key});

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  late final VenueSeatController<VenueSeat, Object> controller;
  bool editing = Uri.base.queryParameters['mode'] == 'editor';
  bool loading = true;
  int nextCustomSeatId = 1000;
  Set<Object> selectedSeatIds = {};

  @override
  void initState() {
    super.initState();
    controller = VenueSeatController<VenueSeat, Object>(
      adapter: venueSeatAdapter,
    );
    _loadVenue();
  }

  Future<void> _loadVenue() async {
    final values =
        jsonDecode(await rootBundle.loadString('assets/venue.json'))
            as Map<String, Object?>;
    final backdrop = await rootBundle.loadString('assets/venue_floor_plan.svg');
    final seats = (values['seats']! as List<Object?>)
        .cast<Map<String, Object?>>()
        .map(VenueSeat.fromJson);
    controller.loadPlan(
      rows: values['rows']! as int,
      columns: values['columns']! as int,
      seats: seats,
      backdrop: SvgVenueBackdrop(backdrop),
    );
    if (mounted) setState(() => loading = false);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final compact = MediaQuery.sizeOf(context).width < 600;
    return Scaffold(
      appBar: AppBar(
        title: Text(compact ? 'Seat map' : 'Venue seat picker'),
        actions: [
          if (!editing)
            Padding(
              padding: EdgeInsets.only(right: compact ? 4 : 12),
              child: Chip(
                avatar: const Icon(Icons.event_seat, size: 18),
                label: Text(
                  compact
                      ? '${selectedSeatIds.length} / 4'
                      : '${selectedSeatIds.length} / 4 selected',
                ),
              ),
            ),
          if (compact)
            IconButton(
              tooltip: editing ? 'Open picker' : 'Open editor',
              onPressed: () => setState(() => editing = !editing),
              icon: Icon(editing ? Icons.event_seat : Icons.edit_outlined),
            )
          else ...[
            const Text('Edit'),
            Switch(
              value: editing,
              onChanged: (value) => setState(() => editing = value),
            ),
          ],
          const SizedBox(width: 4),
        ],
      ),
      body: Padding(
        padding: compact
            ? const EdgeInsets.fromLTRB(8, 8, 8, 12)
            : const EdgeInsets.fromLTRB(24, 12, 24, 20),
        child: Column(
          children: [
            Text(
              editing
                  ? 'Choose a tool, then tap seats to edit the plan.'
                  : compact
                  ? 'Tap a seat to select it. Pinch to zoom.'
                  : 'Click available seats to select them. Scroll to zoom.',
              style: Theme.of(context).textTheme.bodyMedium,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 8),
            Expanded(
              child: loading
                  ? const Center(child: CircularProgressIndicator())
                  : editing
                  ? VenueSeatEditor<VenueSeat, Object>(
                      controller: controller,
                      config: VenueSeatViewConfig.fromTheme(context),
                      editing: SeatEditingDelegate<VenueSeat>(
                        create: (position, status) => VenueSeat(
                          id: 'custom-${nextCustomSeatId++}',
                          position: position,
                          status: status,
                          label: 'New ${position.row}:${position.column}',
                        ),
                        withStatus: (seat, status) =>
                            seat.copyWith(status: status),
                      ),
                    )
                  : VenueSeatPicker<VenueSeat, Object>(
                      controller: controller,
                      config: VenueSeatViewConfig.fromTheme(context),
                      maxSelectedSeats: 4,
                      onSelectionRequested: (request) async {
                        await Future<void>.delayed(
                          const Duration(milliseconds: 350),
                        );
                        return true;
                      },
                      onSelectionChanged: (selection) =>
                          setState(() => selectedSeatIds = selection),
                      onSelectionLimitReached: () =>
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                              content: Text(
                                'You can select at most four seats.',
                              ),
                            ),
                          ),
                    ),
            ),
            if (!editing && !loading) const _Legend(),
          ],
        ),
      ),
    );
  }
}

class _Legend extends StatelessWidget {
  const _Legend();

  @override
  Widget build(BuildContext context) => Padding(
    padding: const EdgeInsets.only(top: 12),
    child: Wrap(
      alignment: WrapAlignment.center,
      spacing: 18,
      runSpacing: 8,
      children: const [
        _LegendItem('Available', SeatStatus.available),
        _LegendItem('Selected', SeatStatus.available, selected: true),
        _LegendItem('Held', SeatStatus.held),
        _LegendItem('Booked', SeatStatus.booked),
        _LegendItem('Checked in', SeatStatus.checkedIn),
        _LegendItem('Blocked', SeatStatus.blocked),
      ],
    ),
  );
}

class _LegendItem extends StatelessWidget {
  const _LegendItem(this.label, this.status, {this.selected = false});

  final String label;
  final SeatStatus status;
  final bool selected;

  @override
  Widget build(BuildContext context) => Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      VenueSeatMarker(status: status, selected: selected, size: 28),
      const SizedBox(width: 4),
      Text(label),
    ],
  );
}
1
likes
150
points
245
downloads

Documentation

API reference

Publisher

verified publisherfestapp.net

Weekly Downloads

Interactive, editable venue seat layouts for Flutter, built for and used in production by Festapp.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#seats #booking #venue #editor #widget

License

MIT (license)

Dependencies

flutter, flutter_svg

More

Packages that depend on venue_seat_picker