dynamic_glass_glmv 0.2.3 copy "dynamic_glass_glmv: ^0.2.3" to clipboard
dynamic_glass_glmv: ^0.2.3 copied to clipboard

A Flutter package for reusable dynamic glass UI surfaces and adaptive platform bottom navigation.

example/lib/main.dart

import 'package:dynamic_glass_glmv/dynamic_glass_glmv.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'dynamic_glass example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: const Color(0xFF0A84FF),
      ),
      home: const _HomeShell(),
    );
  }
}

class _HomeShell extends StatefulWidget {
  const _HomeShell();

  @override
  State<_HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<_HomeShell> {
  int _ordersBadge = 5;

  @override
  Widget build(BuildContext context) {
    return DynamicGlassNavigationScaffold(
      selectedItemColor: const Color(0xFF0A84FF),
      minimizeBehavior: TabBarMinimizeBehavior.never,
      split: true,
      rightCount: 1,
      onIndexChanged: (i) {
        if (i == 1 && _ordersBadge > 0) {
          // Clear the badge once the user opens Orders.
          setState(() => _ordersBadge = 0);
        }
      },
      items: [
        AdaptiveNavigationDestination(
          icon: SvgPicture.asset('assets/icons/box_outlined.svg'),
          selectedIcon: SvgPicture.asset('assets/icons/box_filled.svg'),
          label: 'Home',
        ),
        AdaptiveNavigationDestination(
          icon: SvgPicture.asset('assets/icons/profile.svg'),
          label: 'Orders',
          badgeCount: _ordersBadge > 0 ? _ordersBadge : null,
        ),
        AdaptiveNavigationDestination(
          icon: SvgPicture.asset('assets/icons/profile.svg'),
          label: 'Profile',
        ),
        const AdaptiveNavigationDestination(
          icon: 'magnifyingglass',
          label: 'Search',
          isSearch: true,
          hideLabel: true,
        ),
      ],
      screens: const [
        _ScrollingPage(
          title: 'Home',
          subtitle: 'Scroll to see the iOS 26 tab bar minimize.',
          accent: Color(0xFF0A84FF),
        ),
        _ScrollingPage(
          title: 'Orders',
          subtitle:
              'Tapping this tab cleared the badge — re-run the app to see it again.',
          accent: Color(0xFFFF9F0A),
        ),
        _GlassShowcasePage(),
        _SearchPage(),
      ],
    );
  }
}

/// A page with vertical scrolling content so the iOS 26 minimize-on-scroll
/// behavior has something to respond to.
class _ScrollingPage extends StatelessWidget {
  const _ScrollingPage({
    required this.title,
    required this.subtitle,
    required this.accent,
  });

  final String title;
  final String subtitle;
  final Color accent;

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverAppBar.large(
          title: Text(title),
          backgroundColor: accent.withValues(alpha: 0.15),
          pinned: true,
        ),
        SliverPadding(
          padding: const EdgeInsets.fromLTRB(16, 8, 16, 120),
          sliver: SliverList.builder(
            itemCount: 40,
            itemBuilder: (context, i) {
              if (i == 0) {
                return Padding(
                  padding: const EdgeInsets.only(bottom: 16),
                  child: Text(
                    subtitle,
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                );
              }
              return Card(
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: accent.withValues(alpha: 0.2),
                    child: Text('${i + 1}'),
                  ),
                  title: Text('$title item ${i + 1}'),
                  subtitle: const Text('Tap me — or just scroll.'),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

/// Minimal search surface — pairs with `isSearch: true` so the rightmost tab
/// renders as the detached `UISearchTab` pill on iOS 26.
class _SearchPage extends StatelessWidget {
  const _SearchPage();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16, 24, 16, 120),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              'Search',
              style: Theme.of(context).textTheme.headlineLarge,
            ),
            const SizedBox(height: 16),
            const CupertinoSearchTextField(
              placeholder: 'Type to search…',
            ),
            const SizedBox(height: 24),
            Text(
              'On iOS 26 the rightmost tab is a UISearchTab, drawn as a detached '
              'liquid-glass pill next to the main bar.',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
          ],
        ),
      ),
    );
  }
}

/// Demonstrates the DynamicGlass surface widget plus the native adaptive
/// controls (switch, slider, segmented control, buttons, popup menu, icon)
/// over a colorful background.
class _GlassShowcasePage extends StatefulWidget {
  const _GlassShowcasePage();

  @override
  State<_GlassShowcasePage> createState() => _GlassShowcasePageState();
}

class _GlassShowcasePageState extends State<_GlassShowcasePage> {
  bool _switchOn = true;
  double _sliderValue = 0.4;
  int _segment = 0;
  int _tapCount = 0;
  String _menuChoice = 'None';

  static const _accent = Color(0xFF0A84FF);

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: [
        const DecoratedBox(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [
                Color(0xFFFF375F),
                Color(0xFF0A84FF),
                Color(0xFF30D158),
              ],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
          ),
        ),
        SafeArea(
          child: ListView(
            padding: const EdgeInsets.fromLTRB(24, 24, 24, 120),
            children: [
              Text(
                'DynamicGlass surface',
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                      color: CupertinoColors.white,
                      fontWeight: FontWeight.w700,
                    ),
              ),
              const SizedBox(height: 24),
              DynamicGlass(
                blur: 24,
                opacity: 0.18,
                borderRadius: BorderRadius.circular(24),
                padding: const EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Native adaptive controls — UIKit/AppKit on Apple '
                      'platforms (liquid glass on iOS 26), Flutter elsewhere.',
                      style: TextStyle(
                        color: CupertinoColors.white,
                        fontSize: 15,
                        height: 1.4,
                      ),
                    ),
                    const SizedBox(height: 20),

                    // Switch
                    _ControlRow(
                      label: 'DynamicSwitch',
                      trailing: DynamicSwitch(
                        value: _switchOn,
                        color: _accent,
                        onChanged: (v) => setState(() => _switchOn = v),
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Slider
                    const _ControlLabel('DynamicSlider'),
                    DynamicSlider(
                      value: _sliderValue,
                      color: _accent,
                      // Disabled when the switch is off, to show the
                      // nullable-onChanged disabled convention.
                      onChanged: _switchOn
                          ? (v) => setState(() => _sliderValue = v)
                          : null,
                    ),
                    const SizedBox(height: 16),

                    // Segmented control
                    const _ControlLabel('DynamicSegmentedControl'),
                    DynamicSegmentedControl(
                      labels: const ['Day', 'Week', 'Month'],
                      selectedIndex: _segment,
                      color: _accent,
                      onValueChanged: (i) => setState(() => _segment = i),
                    ),
                    const SizedBox(height: 20),

                    // Buttons
                    Row(
                      children: [
                        DynamicButton(
                          label: 'Tap me ($_tapCount)',
                          tint: _accent,
                          style: DynamicButtonStyle.glass,
                          onPressed: () => setState(() => _tapCount++),
                        ),
                        const SizedBox(width: 12),
                        DynamicButton.icon(
                          icon: const DynamicSymbol('heart.fill'),
                          tint: const Color(0xFFFF375F),
                          onPressed: () => setState(() => _tapCount++),
                        ),
                      ],
                    ),
                    const SizedBox(height: 20),

                    // Popup menu + icon
                    _ControlRow(
                      label: 'Menu: $_menuChoice',
                      trailing: DynamicPopupMenuButton.icon(
                        buttonIcon: const DynamicSymbol('ellipsis.circle'),
                        items: const [
                          DynamicPopupMenuItem(
                            label: 'Share',
                            icon: DynamicSymbol('square.and.arrow.up'),
                          ),
                          DynamicPopupMenuItem(
                            label: 'Duplicate',
                            icon: DynamicSymbol('plus.square.on.square'),
                          ),
                          DynamicPopupMenuDivider(),
                          DynamicPopupMenuItem(
                            label: 'Delete',
                            icon: DynamicSymbol('trash'),
                          ),
                        ],
                        onSelected: (i) => setState(() {
                          _menuChoice =
                              const ['Share', 'Duplicate', 'Delete'][i];
                        }),
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Icon
                    _ControlRow(
                      label: 'DynamicIcon',
                      trailing: const DynamicIcon(
                        symbol: DynamicSymbol(
                          'sparkles',
                          size: 28,
                          color: CupertinoColors.white,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

/// A left-aligned white label used inside the glass showcase card.
class _ControlLabel extends StatelessWidget {
  const _ControlLabel(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        text,
        style: const TextStyle(
          color: CupertinoColors.white,
          fontSize: 13,
          fontWeight: FontWeight.w600,
        ),
      ),
    );
  }
}

/// A label paired with a trailing control on the same row.
class _ControlRow extends StatelessWidget {
  const _ControlRow({required this.label, required this.trailing});

  final String label;
  final Widget trailing;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          child: Text(
            label,
            style: const TextStyle(
              color: CupertinoColors.white,
              fontSize: 15,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),
        trailing,
      ],
    );
  }
}
7
likes
150
points
131
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package for reusable dynamic glass UI surfaces and adaptive platform bottom navigation.

Repository (GitHub)
View/report issues

Topics

#flutter #widget #glassmorphism #blur #ui

License

BSD-3-Clause (license)

Dependencies

cupertino_icons, cupertino_native_glmv, flutter, flutter_svg

More

Packages that depend on dynamic_glass_glmv