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

Animated responsive breadcrumbs synchronized with Flutter Navigator routes and URL-style locations.

example/lib/main.dart

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

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

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

  @override
  State<BreadcrumbStudioApp> createState() => _BreadcrumbStudioAppState();
}

class _BreadcrumbStudioAppState extends State<BreadcrumbStudioApp> {
  late final RouteBreadcrumbController _controller;
  late final RouteBreadcrumbObserver _observer;
  ThemeMode _themeMode = ThemeMode.light;

  @override
  void initState() {
    super.initState();
    _controller = RouteBreadcrumbController();
    _observer = RouteBreadcrumbObserver(
      controller: _controller,
      resolver: _resolveBreadcrumb,
    );
  }

  RouteBreadcrumb? _resolveBreadcrumb(Route<dynamic> route) {
    final name = route.settings.name;
    if (name == null) return null;
    final destination = _destinations[name] ?? _destinations['/']!;
    return RouteBreadcrumb(
      id: route,
      label: destination.label,
      location: name,
      icon: destination.icon,
      semanticLabel: 'Go to ${destination.label}',
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Orbit workspace',
      themeMode: _themeMode,
      theme: _theme(Brightness.light),
      darkTheme: _theme(Brightness.dark),
      navigatorObservers: [_observer],
      onGenerateRoute: (settings) => MaterialPageRoute<void>(
        settings: settings,
        builder: (context) => _DashboardPage(
          routeName: settings.name ?? '/',
          controller: _controller,
          themeMode: _themeMode,
          onToggleTheme: () => setState(() {
            _themeMode = _themeMode == ThemeMode.light
                ? ThemeMode.dark
                : ThemeMode.light;
          }),
        ),
      ),
    );
  }

  ThemeData _theme(Brightness brightness) {
    const seed = Color(0xFF6D5CE7);
    final scheme = ColorScheme.fromSeed(
      seedColor: seed,
      brightness: brightness,
    );
    return ThemeData(
      useMaterial3: true,
      brightness: brightness,
      colorScheme: scheme,
      scaffoldBackgroundColor: brightness == Brightness.light
          ? const Color(0xFFF7F7FC)
          : const Color(0xFF101016),
      cardTheme: CardThemeData(
        elevation: 0,
        margin: EdgeInsets.zero,
        color: brightness == Brightness.light
            ? Colors.white
            : const Color(0xFF191920),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
      ),
      dividerColor: scheme.outlineVariant.withValues(alpha: .55),
    );
  }
}

class _DashboardPage extends StatelessWidget {
  const _DashboardPage({
    required this.routeName,
    required this.controller,
    required this.themeMode,
    required this.onToggleTheme,
  });

  final String routeName;
  final RouteBreadcrumbController controller;
  final ThemeMode themeMode;
  final VoidCallback onToggleTheme;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          child: Align(
            alignment: Alignment.topCenter,
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 1180),
              child: Padding(
                padding: const EdgeInsets.fromLTRB(20, 18, 20, 48),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    _TopBar(
                      routeName: routeName,
                      themeMode: themeMode,
                      onToggleTheme: onToggleTheme,
                    ),
                    const SizedBox(height: 38),
                    _BreadcrumbHeader(
                      controller: controller,
                      routeName: routeName,
                    ),
                    const SizedBox(height: 30),
                    AnimatedSwitcher(
                      duration: const Duration(milliseconds: 320),
                      switchInCurve: Curves.easeOutCubic,
                      transitionBuilder: (child, animation) => FadeTransition(
                        opacity: animation,
                        child: SlideTransition(
                          position: Tween<Offset>(
                            begin: const Offset(0, .025),
                            end: Offset.zero,
                          ).animate(animation),
                          child: child,
                        ),
                      ),
                      child: KeyedSubtree(
                        key: ValueKey(routeName),
                        child: _RouteContent(routeName: routeName),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _TopBar extends StatelessWidget {
  const _TopBar({
    required this.routeName,
    required this.themeMode,
    required this.onToggleTheme,
  });

  final String routeName;
  final ThemeMode themeMode;
  final VoidCallback onToggleTheme;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const _Logo(size: 40),
        const SizedBox(width: 12),
        Expanded(
          child: Text(
            'Breadcrumb Studio',
            overflow: TextOverflow.ellipsis,
            style: Theme.of(
              context,
            ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w900),
          ),
        ),
        PopupMenuButton<String>(
          tooltip: 'Explore demo routes',
          initialValue: routeName,
          onSelected: (route) => _navigateTo(context, route),
          itemBuilder: (context) => [
            for (final entry in _destinations.entries)
              PopupMenuItem(
                value: entry.key,
                child: Row(
                  children: [
                    Icon(entry.value.icon, size: 19),
                    const SizedBox(width: 10),
                    Text(entry.value.label),
                  ],
                ),
              ),
          ],
          icon: const Icon(Icons.explore_outlined),
        ),
        IconButton(
          tooltip: themeMode == ThemeMode.dark ? 'Light mode' : 'Dark mode',
          onPressed: onToggleTheme,
          icon: Icon(
            themeMode == ThemeMode.dark
                ? Icons.light_mode_outlined
                : Icons.dark_mode_outlined,
          ),
        ),
        const SizedBox(width: 6),
        const CircleAvatar(
          radius: 18,
          backgroundColor: Color(0xFFE7E2FF),
          child: Text(
            'GS',
            style: TextStyle(
              color: Color(0xFF4E3FB5),
              fontSize: 12,
              fontWeight: FontWeight.w800,
            ),
          ),
        ),
      ],
    );
  }
}

class _BreadcrumbHeader extends StatelessWidget {
  const _BreadcrumbHeader({required this.controller, required this.routeName});

  final RouteBreadcrumbController controller;
  final String routeName;

  @override
  Widget build(BuildContext context) {
    final destination = _destinations[routeName] ?? _destinations['/']!;
    final colors = Theme.of(context).colorScheme;
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        DecoratedBox(
          decoration: BoxDecoration(
            color: Theme.of(context).cardTheme.color,
            borderRadius: BorderRadius.circular(18),
            border: Border.all(
              color: colors.outlineVariant.withValues(alpha: .6),
            ),
          ),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
            child: AnimatedRouteBreadcrumbs(
              breadcrumbs: controller,
              compactBreakpoint: 600,
              maximumVisibleBreadcrumbs: 4,
              theme: AnimatedRouteBreadcrumbThemeData(
                backgroundColor: Colors.transparent,
                foregroundColor: colors.onSurfaceVariant,
                currentBackgroundColor: colors.primaryContainer,
                currentForegroundColor: colors.onPrimaryContainer,
                separatorColor: colors.outline,
                borderRadius: BorderRadius.circular(11),
                padding: const EdgeInsets.symmetric(
                  horizontal: 11,
                  vertical: 8,
                ),
              ),
            ),
          ),
        ),
        const SizedBox(height: 26),
        Text(
          destination.label,
          style: Theme.of(context).textTheme.headlineMedium?.copyWith(
            fontWeight: FontWeight.w900,
            letterSpacing: -.7,
          ),
        ),
        const SizedBox(height: 7),
        Text(
          destination.description,
          style: Theme.of(
            context,
          ).textTheme.bodyLarge?.copyWith(color: colors.onSurfaceVariant),
        ),
      ],
    );
  }
}

class _Logo extends StatelessWidget {
  const _Logo({this.size = 42});

  final double size;

  @override
  Widget build(BuildContext context) => Container(
    width: size,
    height: size,
    decoration: BoxDecoration(
      gradient: const LinearGradient(
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
        colors: [Color(0xFF8B7CF6), Color(0xFF5845D6)],
      ),
      borderRadius: BorderRadius.circular(size * .31),
    ),
    child: Icon(Icons.route_rounded, size: size * .55, color: Colors.white),
  );
}

class _RouteContent extends StatelessWidget {
  const _RouteContent({required this.routeName});

  final String routeName;

  @override
  Widget build(BuildContext context) {
    return switch (routeName) {
      '/projects' => const _ProjectsView(),
      '/projects/animated-route-breadcrumbs' => const _PackageView(),
      '/projects/animated-route-breadcrumbs/analytics' =>
        const _AnalyticsView(),
      _ => const _OverviewView(),
    };
  }
}

class _OverviewView extends StatelessWidget {
  const _OverviewView();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const _ResponsiveGrid(
          minItemWidth: 190,
          children: [
            _MetricCard(
              label: 'Active projects',
              value: '12',
              change: '+3 this month',
              icon: Icons.layers_outlined,
              accent: Color(0xFF6D5CE7),
            ),
            _MetricCard(
              label: 'Team members',
              value: '24',
              change: '4 online now',
              icon: Icons.people_alt_outlined,
              accent: Color(0xFFEB7B5C),
            ),
            _MetricCard(
              label: 'Completion',
              value: '86%',
              change: '+8.4% growth',
              icon: Icons.auto_graph_rounded,
              accent: Color(0xFF20A882),
            ),
          ],
        ),
        const SizedBox(height: 18),
        _SectionCard(
          title: 'Recent projects',
          subtitle: 'Continue where your team left off.',
          action: FilledButton.icon(
            onPressed: () => _openRoute(context, '/projects'),
            icon: const Icon(Icons.arrow_forward_rounded, size: 18),
            label: const Text('View all'),
          ),
          child: Column(
            children: [
              _ProjectRow(
                icon: Icons.route_rounded,
                color: const Color(0xFF6D5CE7),
                title: 'Animated route breadcrumbs',
                subtitle: 'Flutter package · Updated today',
                progress: .86,
                onTap: () => _openProject(context),
              ),
              const Divider(height: 24),
              const _ProjectRow(
                icon: Icons.web_rounded,
                color: Color(0xFFEB7B5C),
                title: 'Portfolio redesign',
                subtitle: 'Flutter web · Updated yesterday',
                progress: .64,
              ),
              const Divider(height: 24),
              const _ProjectRow(
                icon: Icons.phone_android_rounded,
                color: Color(0xFF20A882),
                title: 'Mobile design system',
                subtitle: 'Components · Updated 3 days ago',
                progress: .42,
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class _ProjectsView extends StatelessWidget {
  const _ProjectsView();

  @override
  Widget build(BuildContext context) {
    return _ResponsiveGrid(
      minItemWidth: 270,
      children: [
        _ProjectTile(
          icon: Icons.route_rounded,
          color: const Color(0xFF6D5CE7),
          title: 'Animated route breadcrumbs',
          category: 'Flutter package',
          status: 'In review',
          onTap: () => _openProject(context),
        ),
        const _ProjectTile(
          icon: Icons.space_dashboard_outlined,
          color: Color(0xFFEB7B5C),
          title: 'Portfolio dashboard',
          category: 'Flutter web',
          status: 'Published',
        ),
        const _ProjectTile(
          icon: Icons.widgets_outlined,
          color: Color(0xFF20A882),
          title: 'UI component lab',
          category: 'Design system',
          status: 'Planning',
        ),
      ],
    );
  }
}

class _PackageView extends StatelessWidget {
  const _PackageView();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _SectionCard(
          title: 'animated_route_breadcrumbs',
          subtitle: 'A polished navigation trail for Flutter applications.',
          action: FilledButton.icon(
            onPressed: () => _openAnalytics(context),
            icon: const Icon(Icons.query_stats_rounded, size: 18),
            label: const Text('View insights'),
          ),
          child: const _PackageHero(),
        ),
        const SizedBox(height: 18),
        const _ResponsiveGrid(
          minItemWidth: 180,
          children: [
            _MetricCard(
              label: 'Pub points',
              value: '160',
              change: 'Perfect score',
              icon: Icons.verified_outlined,
              accent: Color(0xFF20A882),
            ),
            _MetricCard(
              label: 'Platforms',
              value: '6',
              change: 'Flutter supported',
              icon: Icons.devices_rounded,
              accent: Color(0xFF6D5CE7),
            ),
            _MetricCard(
              label: 'Dependencies',
              value: '0',
              change: 'Lightweight API',
              icon: Icons.bolt_rounded,
              accent: Color(0xFFEB7B5C),
            ),
          ],
        ),
      ],
    );
  }
}

class _AnalyticsView extends StatelessWidget {
  const _AnalyticsView();

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    return Column(
      children: [
        const _ResponsiveGrid(
          minItemWidth: 190,
          children: [
            _MetricCard(
              label: 'Weekly views',
              value: '2.4K',
              change: '+18.2%',
              icon: Icons.visibility_outlined,
              accent: Color(0xFF6D5CE7),
            ),
            _MetricCard(
              label: 'Installs',
              value: '842',
              change: '+12.8%',
              icon: Icons.download_done_rounded,
              accent: Color(0xFF20A882),
            ),
            _MetricCard(
              label: 'Likes',
              value: '126',
              change: '+9 this week',
              icon: Icons.favorite_border_rounded,
              accent: Color(0xFFEB7B5C),
            ),
          ],
        ),
        const SizedBox(height: 18),
        _SectionCard(
          title: 'Package activity',
          subtitle: 'Illustrative data for the interactive package example.',
          child: SizedBox(
            height: 230,
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                for (final item in const [35, 52, 44, 72, 61, 88, 76])
                  Expanded(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 6),
                      child: TweenAnimationBuilder<double>(
                        tween: Tween(begin: 0, end: item.toDouble()),
                        duration: const Duration(milliseconds: 700),
                        curve: Curves.easeOutCubic,
                        builder: (context, value, _) => Container(
                          height: value * 2,
                          decoration: BoxDecoration(
                            color: colors.primary.withValues(alpha: .18),
                            borderRadius: const BorderRadius.vertical(
                              top: Radius.circular(10),
                            ),
                            border: Border(
                              top: BorderSide(color: colors.primary, width: 3),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

class _ResponsiveGrid extends StatelessWidget {
  const _ResponsiveGrid({required this.children, required this.minItemWidth});

  final List<Widget> children;
  final double minItemWidth;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final columns = (constraints.maxWidth / minItemWidth).floor().clamp(
          1,
          children.length,
        );
        final width = (constraints.maxWidth - (columns - 1) * 16) / columns;
        return Wrap(
          spacing: 16,
          runSpacing: 16,
          children: [
            for (final child in children) SizedBox(width: width, child: child),
          ],
        );
      },
    );
  }
}

class _MetricCard extends StatelessWidget {
  const _MetricCard({
    required this.label,
    required this.value,
    required this.change,
    required this.icon,
    required this.accent,
  });

  final String label;
  final String value;
  final String change;
  final IconData icon;
  final Color accent;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(22),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              width: 42,
              height: 42,
              decoration: BoxDecoration(
                color: accent.withValues(alpha: .12),
                borderRadius: BorderRadius.circular(13),
              ),
              child: Icon(icon, color: accent, size: 21),
            ),
            const SizedBox(height: 20),
            Text(
              value,
              style: Theme.of(
                context,
              ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w900),
            ),
            const SizedBox(height: 3),
            Text(label, style: Theme.of(context).textTheme.bodyMedium),
            const SizedBox(height: 12),
            Text(
              change,
              style: TextStyle(
                color: accent,
                fontSize: 12,
                fontWeight: FontWeight.w700,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _SectionCard extends StatelessWidget {
  const _SectionCard({
    required this.title,
    required this.subtitle,
    required this.child,
    this.action,
  });

  final String title;
  final String subtitle;
  final Widget child;
  final Widget? action;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            LayoutBuilder(
              builder: (context, constraints) {
                final information = Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: Theme.of(context).textTheme.titleLarge?.copyWith(
                        fontWeight: FontWeight.w900,
                      ),
                    ),
                    const SizedBox(height: 5),
                    Text(
                      subtitle,
                      style: TextStyle(
                        color: Theme.of(context).colorScheme.onSurfaceVariant,
                      ),
                    ),
                  ],
                );
                if (action == null) return information;
                if (constraints.maxWidth < 540) {
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      information,
                      const SizedBox(height: 16),
                      action!,
                    ],
                  );
                }
                return Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Expanded(child: information),
                    const SizedBox(width: 12),
                    action!,
                  ],
                );
              },
            ),
            const SizedBox(height: 26),
            child,
          ],
        ),
      ),
    );
  }
}

class _ProjectRow extends StatelessWidget {
  const _ProjectRow({
    required this.icon,
    required this.color,
    required this.title,
    required this.subtitle,
    required this.progress,
    this.onTap,
  });

  final IconData icon;
  final Color color;
  final String title;
  final String subtitle;
  final double progress;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      borderRadius: BorderRadius.circular(16),
      onTap: onTap,
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 4),
        child: Row(
          children: [
            Container(
              width: 46,
              height: 46,
              decoration: BoxDecoration(
                color: color.withValues(alpha: .12),
                borderRadius: BorderRadius.circular(14),
              ),
              child: Icon(icon, color: color),
            ),
            const SizedBox(width: 14),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: const TextStyle(fontWeight: FontWeight.w800),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    subtitle,
                    style: TextStyle(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                      fontSize: 12,
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(
              width: 76,
              child: LinearProgressIndicator(
                value: progress,
                color: color,
                backgroundColor: color.withValues(alpha: .12),
                borderRadius: BorderRadius.circular(10),
              ),
            ),
            const SizedBox(width: 10),
            const Icon(Icons.chevron_right_rounded),
          ],
        ),
      ),
    );
  }
}

class _ProjectTile extends StatelessWidget {
  const _ProjectTile({
    required this.icon,
    required this.color,
    required this.title,
    required this.category,
    required this.status,
    this.onTap,
  });

  final IconData icon;
  final Color color;
  final String title;
  final String category;
  final String status;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Container(
                    width: 48,
                    height: 48,
                    decoration: BoxDecoration(
                      color: color.withValues(alpha: .12),
                      borderRadius: BorderRadius.circular(14),
                    ),
                    child: Icon(icon, color: color),
                  ),
                  const Spacer(),
                  const Icon(Icons.arrow_outward_rounded, size: 20),
                ],
              ),
              const SizedBox(height: 28),
              Text(
                title,
                style: Theme.of(
                  context,
                ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w900),
              ),
              const SizedBox(height: 7),
              Text(
                category,
                style: TextStyle(
                  color: Theme.of(context).colorScheme.onSurfaceVariant,
                ),
              ),
              const SizedBox(height: 22),
              DecoratedBox(
                decoration: BoxDecoration(
                  color: color.withValues(alpha: .1),
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 10,
                    vertical: 6,
                  ),
                  child: Text(
                    status,
                    style: TextStyle(
                      color: color,
                      fontSize: 12,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _PackageHero extends StatelessWidget {
  const _PackageHero();

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 28,
      runSpacing: 24,
      crossAxisAlignment: WrapCrossAlignment.center,
      children: [
        Container(
          width: 150,
          height: 150,
          decoration: BoxDecoration(
            gradient: const LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [Color(0xFF8B7CF6), Color(0xFF5845D6)],
            ),
            borderRadius: BorderRadius.circular(34),
          ),
          child: const Icon(Icons.route_rounded, color: Colors.white, size: 68),
        ),
        ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 460),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Navigator-aware. Responsive. Animated.',
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                  fontWeight: FontWeight.w900,
                ),
              ),
              const SizedBox(height: 12),
              Text(
                'The trail above is the actual package. Open Insights and tap '
                'any ancestor to jump back through the Navigator stack.',
                style: TextStyle(
                  height: 1.55,
                  color: Theme.of(context).colorScheme.onSurfaceVariant,
                ),
              ),
              const SizedBox(height: 18),
              const Wrap(
                spacing: 8,
                runSpacing: 8,
                children: [
                  Chip(label: Text('Navigator 1.0')),
                  Chip(label: Text('go_router ready')),
                  Chip(label: Text('Responsive')),
                  Chip(label: Text('Accessible')),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class _Destination {
  const _Destination(this.label, this.description, this.icon);

  final String label;
  final String description;
  final IconData icon;
}

const _destinations = <String, _Destination>{
  '/': _Destination(
    'Overview',
    'A clear view of your workspace and latest activity.',
    Icons.home_outlined,
  ),
  '/projects': _Destination(
    'Projects',
    'Everything your team is designing, building, and shipping.',
    Icons.folder_copy_outlined,
  ),
  '/projects/animated-route-breadcrumbs': _Destination(
    'Route breadcrumbs',
    'Package details, quality signals, and release readiness.',
    Icons.route_rounded,
  ),
  '/projects/animated-route-breadcrumbs/analytics': _Destination(
    'Analytics',
    'Track adoption and engagement across every supported platform.',
    Icons.query_stats_rounded,
  ),
};

void _navigateTo(BuildContext context, String route) {
  switch (route) {
    case '/':
      _goRoot(context);
    case '/projects':
      _openRoute(context, route);
    case '/projects/animated-route-breadcrumbs':
      _openProject(context);
    case '/projects/animated-route-breadcrumbs/analytics':
      _openDeepRoute(context);
  }
}

void _goRoot(BuildContext context, {bool closeDrawer = false}) {
  final navigator = Navigator.of(context);
  if (closeDrawer) navigator.pop();
  navigator.popUntil((route) => route.isFirst);
}

void _openRoute(
  BuildContext context,
  String route, {
  bool closeDrawer = false,
}) {
  final navigator = Navigator.of(context);
  final currentRoute = ModalRoute.of(context)?.settings.name;
  if (closeDrawer) navigator.pop();
  if (currentRoute == route) return;
  navigator.popUntil((candidate) => candidate.isFirst);
  if (route != '/') navigator.pushNamed(route);
}

void _openProject(BuildContext context) {
  final navigator = Navigator.of(context);
  navigator.popUntil((route) => route.isFirst);
  navigator.pushNamed('/projects');
  navigator.pushNamed('/projects/animated-route-breadcrumbs');
}

void _openAnalytics(BuildContext context) {
  Navigator.pushNamed(
    context,
    '/projects/animated-route-breadcrumbs/analytics',
  );
}

void _openDeepRoute(BuildContext context, {bool closeDrawer = false}) {
  final navigator = Navigator.of(context);
  if (closeDrawer) navigator.pop();
  navigator.popUntil((route) => route.isFirst);
  navigator.pushNamed('/projects');
  navigator.pushNamed('/projects/animated-route-breadcrumbs');
  navigator.pushNamed('/projects/animated-route-breadcrumbs/analytics');
}
1
likes
160
points
33
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Animated responsive breadcrumbs synchronized with Flutter Navigator routes and URL-style locations.

Homepage
Repository (GitHub)
View/report issues

Topics

#breadcrumbs #navigation #animation #routing #responsive

License

MIT (license)

Dependencies

flutter

More

Packages that depend on animated_route_breadcrumbs