Skip to main content

flutter_bracket 0.1.0 copy "flutter_bracket: ^0.1.0" to clipboard
flutter_bracket: ^0.1.0 copied to clipboard

Generic graph-driven elimination brackets with pan, zoom, custom cards and themes.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bracket/flutter_bracket.dart';

import 'sample_brackets.dart';

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

const examples = [
  'Single 8',
  'Single 32',
  'Single 128',
  'Single 256',
  'BYE',
  'Incomplete',
  'Double',
  'Custom Card',
  'Themes',
  'Single 1024',
  'Double 128',
  'Double 256',
  'Double 1024',
];

class BracketExampleApp extends StatefulWidget {
  const BracketExampleApp({super.key});
  @override
  State<BracketExampleApp> createState() => _BracketExampleAppState();
}

class _BracketExampleAppState extends State<BracketExampleApp> {
  bool _dark = false;
  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'flutter_bracket',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xff0f766e)),
    ),
    darkTheme: ThemeData(
      colorScheme: ColorScheme.fromSeed(
        seedColor: const Color(0xff5eead4),
        brightness: Brightness.dark,
      ),
    ),
    themeMode: _dark ? ThemeMode.dark : ThemeMode.light,
    home: ExamplePage(onThemeToggle: () => setState(() => _dark = !_dark)),
  );
}

class ExamplePage extends StatefulWidget {
  const ExamplePage({super.key, required this.onThemeToggle});
  final VoidCallback onThemeToggle;
  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  final _controller = BracketViewportController();
  int _page = 0;
  int _score = 18;
  String? _selected;
  bool _virtualization = true;
  bool _overview = true;
  final _stats = ValueNotifier<BracketRenderStats?>(null);
  late Bracket _bracket = _createBracket;

  void _onStats(BracketRenderStats stats) => _stats.value = stats;

  @override
  void dispose() {
    _controller.dispose();
    _stats.dispose();
    super.dispose();
  }

  Bracket get _createBracket => switch (_page) {
    9 => single(1024, score: _score),
    10 => doubleFor(128),
    11 => doubleFor(256),
    12 => doubleFor(1024),
    1 => single(32, score: _score, status: BracketMatchStatus.running),
    2 => single(128, score: _score, status: BracketMatchStatus.running),
    3 => single(256, score: _score, status: BracketMatchStatus.running),
    4 => single(8, bye: true, score: _score),
    5 => Bracket(
      id: 'incomplete',
      sections: [
        BracketSection(
          id: 'main',
          type: BracketSectionType.main,
          rounds: [
            BracketRound(
              id: 'r1',
              order: 0,
              matches: [
                match('a', winnerTo: target('c')),
                match('b', order: 1),
              ],
            ),
            BracketRound(
              id: 'r2',
              order: 1,
              matches: [
                match(
                  'c',
                  side1: const BracketSide(source: WinnerOfSource('a')),
                ),
              ],
            ),
          ],
        ),
      ],
    ),
    6 => doubleElimination(),
    _ => single(8, score: _score, status: BracketMatchStatus.running),
  };

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: const Text('flutter_bracket'),
      actions: [
        IconButton(
          tooltip: 'Toggle theme',
          onPressed: widget.onThemeToggle,
          icon: const Icon(Icons.brightness_6_outlined),
        ),
      ],
    ),
    body: Column(
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Wrap(
            spacing: 12,
            runSpacing: 8,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              DropdownButton<int>(
                value: _page,
                items: [
                  for (var i = 0; i < examples.length; i++)
                    DropdownMenuItem(value: i, child: Text(examples[i])),
                ],
                onChanged: (page) {
                  if (page == null) return;
                  setState(() {
                    _page = page;
                    _selected = null;
                    _bracket = _createBracket;
                  });
                  WidgetsBinding.instance.addPostFrameCallback((_) {
                    Future<void>.microtask(() {
                      if (mounted) _controller.fit();
                    });
                  });
                },
              ),
              TextButton(
                onPressed: _controller.reset,
                child: const Text('Reset'),
              ),
              TextButton(onPressed: _controller.fit, child: const Text('Fit')),
              IconButton(
                tooltip: 'Zoom in',
                onPressed: _controller.zoomIn,
                icon: const Icon(Icons.zoom_in),
              ),
              IconButton(
                tooltip: 'Zoom out',
                onPressed: _controller.zoomOut,
                icon: const Icon(Icons.zoom_out),
              ),
              TextButton(
                onPressed: () => setState(() {
                  _score = (_score + 1) % 30;
                  _bracket = updateMatchScore(
                    _bracket,
                    _bracket.sections.first.rounds.first.matches.first.id,
                    _score,
                  );
                }),
                child: const Text('Update score'),
              ),
              TextButton(
                onPressed: () => _controller.focusMatch(
                  _page == 6 || _page >= 10
                      ? 'gf1'
                      : _page == 5
                      ? 'c'
                      : 'm1',
                ),
                child: const Text('Focus match'),
              ),
              FilterChip(
                label: const Text('Virtualization'),
                selected: _virtualization,
                onSelected: (value) => setState(() => _virtualization = value),
              ),
              FilterChip(
                label: const Text('Overview'),
                selected: _overview,
                onSelected: (value) => setState(() => _overview = value),
              ),
            ],
          ),
        ),
        Expanded(
          child: BracketView(
            bracket: _bracket,
            controller: _controller,
            renderConfig: BracketRenderConfig(
              virtualizationEnabled: _virtualization,
              overviewEnabled: _overview,
            ),
            onRenderStats: _onStats,
            selectedMatchId: _selected,
            highlightedMatchIds: const {'m1', 'm5'},
            onMatchTap: (match) => setState(() => _selected = match.id),
            theme: _page == 8
                ? const BracketTheme(
                    cardColor: Color(0xffe0f2fe),
                    borderColor: Color(0xff0284c7),
                    winnerConnectorColor: Color(0xff0284c7),
                    loserConnectorColor: Color(0xfff97316),
                    primaryTextStyle: TextStyle(color: Color(0xff0c4a6e)),
                    borderRadius: 16,
                    connectorWidth: 3,
                  )
                : const BracketTheme(),
            matchBuilder: _page == 7
                ? (context, match, state) => Semantics(
                    label: 'Custom match ${match.id}',
                    child: DecoratedBox(
                      decoration: BoxDecoration(
                        gradient: const LinearGradient(
                          colors: [Color(0xff115e59), Color(0xff0f766e)],
                        ),
                        borderRadius: BorderRadius.circular(16),
                        border: Border.all(
                          color: state.isSelected
                              ? Colors.amber
                              : Colors.teal.shade200,
                          width: 2,
                        ),
                      ),
                      child: Padding(
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              'MATCH ${match.id.toUpperCase()}',
                              style: const TextStyle(
                                color: Colors.white70,
                                fontSize: 11,
                              ),
                            ),
                            const Spacer(),
                            Text(
                              match.side1.participant?.displayName ??
                                  'Qualifier A',
                              style: const TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            Text(
                              match.side2.participant?.displayName ??
                                  'Qualifier B',
                              style: const TextStyle(color: Colors.white),
                            ),
                          ],
                        ),
                      ),
                    ),
                  )
                : null,
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(12),
          child: ValueListenableBuilder<BracketRenderStats?>(
            valueListenable: _stats,
            builder: (context, stats, _) => Text(
              '${stats?.mountedMatchCount ?? 0} cards mounted · ${stats?.isOverview == true ? 'Overview' : 'Detail'} · '
              '${_selected == null ? 'Pan or pinch to explore' : 'Selected: $_selected'}',
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ),
        ),
      ],
    ),
  );
}
1
likes
150
points
267
downloads
screenshot

Documentation

API reference

Publisher

verified publishertuanha.dev

Weekly Downloads

Generic graph-driven elimination brackets with pan, zoom, custom cards and themes.

Repository (GitHub)
View/report issues
Contributing

Topics

#tournament #bracket #sports #visualization #flutter

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_bracket