contextual_bottom_bar 0.0.2 copy "contextual_bottom_bar: ^0.0.2" to clipboard
contextual_bottom_bar: ^0.0.2 copied to clipboard

A Flutter bottom navigation bar that switches between global and section bars.

example/lib/main.dart

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

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

/// sectionId(K) 예시: enum (타입 안전)
enum DemoSectionId { ticket }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'contextual_bottom_bar demo',
      theme: ThemeData.light(useMaterial3: true),
      home: const DemoHome(),
    );
  }
}

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

  @override
  State<DemoHome> createState() => _DemoHomeState();
}

class _DemoHomeState extends State<DemoHome> {
  ContextualBarMode _mode = ContextualBarMode.global;

  int _globalIndex = 0;

  // 현재 섹션 (mode가 global이어도 “마지막 섹션”을 들고 있어도 됨)
  DemoSectionId _sectionId = DemoSectionId.ticket;

  // 섹션별 마지막 탭 기억 (요구사항: 섹션도 기억 UX 통일)
  final Map<DemoSectionId, int> _sectionIndexById = {
    DemoSectionId.ticket: 0,
  };

  // 현재 섹션에서 선택된 탭 (sectionId에 맞춰 map에서 가져옴)
  int get _sectionIndex => _sectionIndexById[_sectionId] ?? 0;

  // global 탭 구성 (예시)
  late final List<ContextualBarItem> _globalItems = [
    ContextualBarItem(
      id: 'home',
      unselectedWidget: const Icon(Icons.home_outlined),
      selectedWidget: const Icon(Icons.home),
      label: '홈',
    ),
    ContextualBarItem(
      id: 'benefit',
      unselectedWidget: const Icon(Icons.shopping_bag_outlined),
      selectedWidget: const Icon(Icons.shopping_bag),
      label: '혜택',
    ),
    ContextualBarItem(
      id: 'coupon',
      unselectedWidget: const Icon(Icons.show_chart),
      label: '쿠폰',
    ),
    ContextualBarItem(
      id: DemoSectionId.ticket,
      unselectedWidget: const Icon(Icons.confirmation_num_outlined),
      selectedWidget: const Icon(Icons.confirmation_num),
      label: '티켓',
    ),
    ContextualBarItem(
      id: 'myCoupon',
      unselectedWidget: const Icon(Icons.card_giftcard_outlined),
      selectedWidget: const Icon(Icons.card_giftcard),
      label: '내쿠폰함',
    ),
  ];

  List<ContextualBarItem> _sectionItems(DemoSectionId id) {
    switch (id) {
      case DemoSectionId.ticket:
        return const [
          ContextualBarItem(
            id: 'ticket',
            unselectedWidget: Icon(Icons.show_chart),
            label: '티켓',
          ),
          ContextualBarItem(
            id: 'watch',
            unselectedWidget: Icon(Icons.star_border),
            selectedWidget: Icon(Icons.star),
            label: '관심',
          ),
          ContextualBarItem(
            id: 'discover',
            unselectedWidget: Icon(Icons.explore_outlined),
            selectedWidget: Icon(Icons.explore),
            label: '발견',
          ),
          ContextualBarItem(
            id: 'feed',
            unselectedWidget: Icon(Icons.article_outlined),
            selectedWidget: Icon(Icons.article),
            label: '피드',
          ),
        ];
    }
  }

  String _screenTitle() {
    if (_mode == ContextualBarMode.global) {
      return 'GLOBAL: ${_globalItems[_globalIndex].label}';
    }
    final items = _sectionItems(_sectionId);
    final idx = _sectionIndex.clamp(0, items.length - 1);
    return 'SECTION(${_sectionId.name}): ${items[idx].label}';
  }

  void _handleTap(ContextualBarMode mode, int index, Object id) {
    setState(() {
      debugPrint('[demo] onTap mode=$mode index=$index id=$id');
      if (mode == ContextualBarMode.global) {
        _globalIndex = index;

        // global에서 “쇼핑/증권” 탭을 누르면 section 모드로 진입
        if (id is DemoSectionId) {
          _sectionId = id; // stocks or shopping
          _mode = ContextualBarMode.section;
          // sectionIndex는 섹션별 마지막 기억값을 그대로 사용
          _sectionIndexById[_sectionId] = _sectionIndexById[_sectionId] ?? 0;
        } else {
          // 홈 같은 전역 화면이면 global 유지
          _mode = ContextualBarMode.global;
        }
      } else {
        // section 탭 클릭: 섹션별 마지막 탭 기억
        _sectionIndexById[_sectionId] = index;
      }
    });
  }

  void _handleBack() {
    setState(() {
      debugPrint('[demo] back to global');
      _mode = ContextualBarMode.global; // globalIndex는 유지(너가 선택한 UX)
    });
  }

  @override
  Widget build(BuildContext context) {
    final sectionItems = _mode == ContextualBarMode.section
        ? _sectionItems(_sectionId)
        : const <ContextualBarItem>[];

    return Scaffold(
      appBar: AppBar(
        title: Text(_screenTitle()),
      ),
      body: Center(
        child: Text(
          _screenTitle(),
          style: const TextStyle(fontSize: 18),
        ),
      ),
      bottomNavigationBar: ContextualBottomBar<DemoSectionId>(
        mode: _mode,
        sectionId: _sectionId,
        globalIndex: _globalIndex,
        sectionIndex: _sectionIndex,
        globalItems: _globalItems,
        sectionItems: sectionItems,
        onTap: _handleTap,
        sectionLeading: _mode == ContextualBarMode.section
            ? Container(
                decoration: const BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(999)),
                  color: Color.fromARGB(255, 189, 189, 189),
                ),
                child: IconButton(
                    onPressed: _handleBack, icon: const Icon(Icons.arrow_back)),
              )
            : null,
        // 옵션들(너가 정한 기본 철학대로 필요 시만 만져)
        reserveLeadingSpace: false,

        useSafeArea: false,
        labelVisibility: ContextualBarLabelVisibility.always,
        style: const ContextualBarStyle(
          backgroundColor: Colors.transparent,
          selectedColor: Colors.black,
          unselectedColor: Colors.green,
        ),
        transition: const ContextualBarTransition(
          preset: ContextualBarTransitionPreset.fade, // default
          slideDirection: ContextualBarSlideDirection.rtl, // default
          duration: Duration(milliseconds: 250),
        ),
        ignoreInputDuringTransition: true,

        surfaceBuilder: (context, mode, child) {
          final bottomInset = MediaQuery.paddingOf(context).bottom;

          if (mode == ContextualBarMode.global) {
            // GLOBAL: 배경은 하단까지 꽉(홈 인디케이터 아래까지),
            // 컨텐츠만 bottomInset 만큼 올려서 안전하게.
            return DecoratedBox(
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.vertical(top: Radius.circular(26)),
                boxShadow: [
                  BoxShadow(
                    blurRadius: 24,
                    offset: Offset(0, -6),
                    color: Color(0x22000000),
                  ),
                ],
              ),
              child: Padding(
                padding: EdgeInsets.only(bottom: bottomInset, top: 12),
                child: SizedBox(height: 56, child: child),
              ),
            );
          }
          return SafeArea(
            child: Padding(
              padding: EdgeInsets.only(left: 16, right: 16, bottom: 12),
              child: DecoratedBox(
                decoration: const BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.all(Radius.circular(30)),
                  boxShadow: [
                    BoxShadow(
                      blurRadius: 24,
                      offset: Offset(0, 10),
                      color: Color(0x22000000),
                    ),
                  ],
                ),
                child: SizedBox(height: 56, child: child),
              ),
            ),
          );
        },
      ),
    );
  }
}
1
likes
160
points
155
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter bottom navigation bar that switches between global and section bars.

Repository (GitHub)
View/report issues

Topics

#flutter #navigation #bottom-navigation-bar #ui #animation

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on contextual_bottom_bar