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

A Flutter bottom navigation package with a configurable arc-notch center action layout.

example/lib/main.dart

import 'package:arc_notch_bottom_nav/arc_notch_bottom_nav.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'arc_notch_bottom_nav example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFF4ECE3)),
        scaffoldBackgroundColor: const Color(0xFFF1E7DB),
        useMaterial3: true,
      ),
      home: const ExampleHomePage(),
    );
  }
}

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

  @override
  State<ExampleHomePage> createState() => _ExampleHomePageState();
}

class _ExampleHomePageState extends State<ExampleHomePage> {
  static const double _iphone17ProMaxWidthMm = 78.0;
  static const double _iphone17ProMaxHeightMm = 163.4;
  static const double _phoneAspectRatio =
      _iphone17ProMaxWidthMm / _iphone17ProMaxHeightMm;

  static const _surfaceColor = Color(0xFFFFFCF7);
  static const _pageBackgroundColor = Color(0xFFF7EFE4);

  int _currentIndex = 0;

  static const items = [
    ArcNotchBottomNavItem(
      id: 'bookshelf',
      label: 'Bookshelf',
      inactiveIcon: CupertinoIcons.book,
      activeIcon: CupertinoIcons.book_fill,
      tooltip: 'Bookshelf',
    ),
    ArcNotchBottomNavItem(
      id: 'stats',
      label: 'Stats',
      inactiveIcon: CupertinoIcons.chart_bar,
      activeIcon: CupertinoIcons.chart_bar_fill,
      badgeData: ArcNotchBottomNavBadgeData.count(count: 8),
    ),
    ArcNotchBottomNavItem(
      id: 'review',
      label: 'Review',
      inactiveIcon: Icons.blur_circular_outlined,
      activeIcon: Icons.blur_circular,
    ),
    ArcNotchBottomNavItem(
      id: 'profile',
      label: 'Profile',
      inactiveIcon: CupertinoIcons.person,
      activeIcon: CupertinoIcons.person_fill,
      badgeData: ArcNotchBottomNavBadgeData.dot(),
    ),
  ];

  static const pages = [
    _ExamplePage(
      title: 'Bookshelf',
      description:
          'Book shelf home, recent reads, category navigation, and more.',
      accentColor: Color(0xFFB98F54),
      icon: CupertinoIcons.book_fill,
      itemExamples: [
        _ExampleItemData(label: 'Bookshelf', icon: CupertinoIcons.book_fill),
        _ExampleItemData(label: 'Recent Reads', icon: CupertinoIcons.time),
        _ExampleItemData(
          label: 'Categories',
          icon: CupertinoIcons.square_grid_2x2,
        ),
      ],
    ),
    _ExamplePage(
      title: 'Stats',
      description:
          'Reading time, check-in trends, completion rate, and other stats.',
      accentColor: Color(0xFF6D8AA6),
      icon: CupertinoIcons.chart_bar_fill,
      itemExamples: [
        _ExampleItemData(
          label: 'Reading Time',
          icon: CupertinoIcons.chart_bar_fill,
        ),
        _ExampleItemData(
          label: 'Completion Rate',
          icon: CupertinoIcons.percent,
        ),
        _ExampleItemData(
          label: 'Check-in Trends',
          icon: CupertinoIcons.graph_square,
        ),
      ],
    ),
    _ExamplePage(
      title: 'Review',
      description:
          'Retrospectives, knowledge review, timeline, and related content.',
      accentColor: Color(0xFF7F8C6B),
      icon: Icons.blur_circular,
      itemExamples: [
        _ExampleItemData(label: 'Retrospect', icon: Icons.blur_circular),
        _ExampleItemData(label: 'Timeline', icon: CupertinoIcons.clock),
        _ExampleItemData(
          label: 'Knowledge Cards',
          icon: CupertinoIcons.doc_text,
        ),
      ],
    ),
    _ExamplePage(
      title: 'Profile',
      description: 'Profile, settings, membership center, and other modules.',
      accentColor: Color(0xFF9C7D7D),
      icon: CupertinoIcons.person_fill,
      itemExamples: [
        _ExampleItemData(label: 'Profile', icon: CupertinoIcons.person_fill),
        _ExampleItemData(label: 'Settings', icon: CupertinoIcons.settings),
        _ExampleItemData(label: 'Membership', icon: CupertinoIcons.star_fill),
      ],
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      body: LayoutBuilder(
        builder: (context, constraints) {
          final maxWidth = (constraints.maxWidth - 32).clamp(320.0, 430.0);
          final maxHeight = constraints.maxHeight - 32;

          var phoneWidth = maxWidth;
          var phoneHeight = phoneWidth / _phoneAspectRatio;

          if (phoneHeight > maxHeight) {
            phoneHeight = maxHeight;
            phoneWidth = phoneHeight * _phoneAspectRatio;
          }

          return Center(
            child: Container(
              width: phoneWidth,
              height: phoneHeight,
              decoration: BoxDecoration(
                color: _surfaceColor,
                borderRadius: BorderRadius.circular(42),
                border: Border.all(
                  color: Colors.white.withValues(alpha: 0.72),
                  width: 1.2,
                ),
                boxShadow: [
                  BoxShadow(
                    color: const Color(0x332D2217),
                    blurRadius: 42,
                    offset: const Offset(0, 24),
                  ),
                ],
              ),
              child: ClipRRect(
                borderRadius: BorderRadius.circular(42),
                child: ColoredBox(
                  color: _pageBackgroundColor,
                  child: Stack(
                    fit: StackFit.expand,
                    children: [
                      Positioned.fill(
                        child: IndexedStack(
                          index: _currentIndex,
                          children: pages,
                        ),
                      ),
                      Positioned(
                        left: 0,
                        right: 0,
                        bottom: 15,
                        child: ArcNotchBottomNav(
                          items: items,
                          currentIndex: _currentIndex,
                          onItemSelected: (index) {
                            setState(() {
                              _currentIndex = index;
                            });
                          },
                          centerAction: ArcNotchBottomNavCenterAction(
                            child: const Icon(
                              CupertinoIcons.pen,
                              size: 24,
                              color: Color(0xFF2E2B27),
                            ),
                            onTap: () {
                              debugPrint('center action tapped');
                            },
                          ),
                          theme: const ArcNotchBottomNavThemeData(
                            backgroundColor: _surfaceColor,
                          ),
                          layout: const ArcNotchBottomNavLayoutData(
                            height: 94,
                            shellHeight: 64,
                            horizontalPadding: 12,
                            contentPadding: EdgeInsets.fromLTRB(18, 12, 18, 8),
                            centerButtonTopOffset: 0,
                            safeAreaMinBottom: 0,
                            maxBarWidth: 398,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

class _ExamplePage extends StatelessWidget {
  const _ExamplePage({
    required this.title,
    required this.description,
    required this.accentColor,
    required this.icon,
    required this.itemExamples,
  });

  final String title;
  final String description;
  final Color accentColor;
  final IconData icon;
  final List<_ExampleItemData> itemExamples;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return SafeArea(
      bottom: false,
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16, 10, 16, 0),
        child: Column(
          children: [
            const _DynamicIsland(),
            const SizedBox(height: 20),
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.fromLTRB(22, 26, 22, 132),
                child: Column(
                  children: [
                    Container(
                      width: 86,
                      height: 86,
                      decoration: BoxDecoration(
                        color: accentColor.withValues(alpha: 0.14),
                        shape: BoxShape.circle,
                      ),
                      alignment: Alignment.center,
                      child: Icon(icon, size: 38, color: accentColor),
                    ),
                    const SizedBox(height: 24),
                    Text(title, style: textTheme.headlineMedium),
                    const SizedBox(height: 14),
                    Text(
                      description,
                      textAlign: TextAlign.center,
                      style: textTheme.bodyLarge?.copyWith(
                        color: const Color(0xFF5F5A53),
                        height: 1.65,
                      ),
                    ),
                    const SizedBox(height: 22),
                    Container(
                      width: double.infinity,
                      padding: const EdgeInsets.all(18),
                      decoration: BoxDecoration(
                        color: Colors.white.withValues(alpha: 0.62),
                        borderRadius: BorderRadius.circular(22),
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'Page & Navigation Mapping',
                            style: textTheme.titleSmall?.copyWith(
                              color: const Color(0xFF4B4339),
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                          const SizedBox(height: 12),
                          Wrap(
                            spacing: 10,
                            runSpacing: 10,
                            children: itemExamples
                                .map(
                                  (item) => _ExampleItemChip(
                                    item: item,
                                    accentColor: accentColor,
                                  ),
                                )
                                .toList(),
                          ),
                          const SizedBox(height: 14),
                          Text(
                            'Each example page corresponds to a bottom navigation item. Replace these with your actual business pages and pass them to ArcNotchBottomNavScaffold.',
                            style: textTheme.bodySmall?.copyWith(
                              color: const Color(0xFF6A6258),
                              height: 1.55,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _DynamicIsland extends StatelessWidget {
  const _DynamicIsland();

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 126,
        height: 34,
        decoration: BoxDecoration(
          color: const Color(0xFF171717),
          borderRadius: BorderRadius.circular(999),
        ),
      ),
    );
  }
}

class _ExampleItemChip extends StatelessWidget {
  const _ExampleItemChip({required this.item, required this.accentColor});

  final _ExampleItemData item;
  final Color accentColor;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      decoration: BoxDecoration(
        color: Colors.white.withValues(alpha: 0.84),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(item.icon, size: 16, color: accentColor),
          const SizedBox(width: 6),
          Text(
            item.label,
            style: Theme.of(context).textTheme.bodySmall?.copyWith(
              color: const Color(0xFF5F5A53),
              fontWeight: FontWeight.w500,
            ),
          ),
        ],
      ),
    );
  }
}

class _ExampleItemData {
  const _ExampleItemData({required this.label, required this.icon});

  final String label;
  final IconData icon;
}
1
likes
150
points
212
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter bottom navigation package with a configurable arc-notch center action layout.

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #widget #navigation #bottom-navigation

License

MIT (license)

Dependencies

cupertino_icons, flutter

More

Packages that depend on arc_notch_bottom_nav