ios_liquid_glass 0.8.0 copy "ios_liquid_glass: ^0.8.0" to clipboard
ios_liquid_glass: ^0.8.0 copied to clipboard

Native iOS 26 Liquid Glass (UIGlassEffect and UITabBarController) for Flutter, with plain no-effect fallbacks on all other platforms.

example/lib/main.dart

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

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LiquidGlassNativeTabShell.instance.initialize();
  runApp(const LiquidGlassExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Liquid Glass Kit',
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF015488),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

/// The four app tabs. On iOS 26+ they become Apple's real `UITabBar` (true
/// Liquid Glass); elsewhere a plain Flutter bar with the same destinations.
/// Rebuilt whenever the badge toggles (see `configureTabs`).
List<LiquidGlassNativeTab> _buildNativeTabs({required bool badge}) => [
  const LiquidGlassNativeTab(title: 'Home', systemImage: 'house', selectedSystemImage: 'house.fill'),
  const LiquidGlassNativeTab(title: 'Search', systemImage: 'magnifyingglass'),
  LiquidGlassNativeTab(
    title: 'Favorites',
    systemImage: 'heart',
    selectedSystemImage: 'heart.fill',
    badge: badge ? '3' : null,
  ),
  const LiquidGlassNativeTab(title: 'Profile', systemImage: 'person.circle', selectedSystemImage: 'person.circle.fill'),
];

const List<LiquidGlassNavItem> _fallbackNavItems = [
  LiquidGlassNavItem(label: 'Home', icon: Icon(Icons.home_outlined), activeIcon: Icon(Icons.home)),
  LiquidGlassNavItem(label: 'Search', icon: Icon(Icons.search_outlined), activeIcon: Icon(Icons.search)),
  LiquidGlassNavItem(label: 'Favorites', icon: Icon(Icons.favorite_outline), activeIcon: Icon(Icons.favorite)),
  LiquidGlassNavItem(label: 'Profile', icon: Icon(Icons.person_outline), activeIcon: Icon(Icons.person)),
];

const LiquidGlassStyle _clearStyle = LiquidGlassStyle(
  nativeVariant: NativeGlassVariant.clear,
);

const LiquidGlassStyle _tintedStyle = LiquidGlassStyle(
  nativeTintColor: Color(0xFF0A84FF),
  plainColor: Color(0xFF0A84FF),
);

const LiquidGlassStyle _darkStyle = LiquidGlassStyle(
  nativeTintColor: Colors.black,
  plainColor: Color(0xFF1C1C1E),
);

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  static const LiquidGlassTheme _theme = LiquidGlassTheme();

  int _currentIndex = 0;
  bool _barVisible = true;
  bool _badgeOn = true;

  final LiquidGlassNativeTabShell _shell = LiquidGlassNativeTabShell.instance;

  bool get _usesNativeTabBar => LiquidGlassPlatform.supportsNativeTabShell;

  List<LiquidGlassNativeTab> get _tabs => _buildNativeTabs(badge: _badgeOn);

  @override
  void initState() {
    super.initState();
    if (_usesNativeTabBar) {
      _shell.onTabSelected = (index) {
        setState(() => _currentIndex = index);
      };
      _shell.ensureInstalled(_tabs, theme: _theme);
    }
  }

  @override
  void dispose() {
    if (_usesNativeTabBar) {
      _shell.onTabSelected = null;
      _shell.uninstallShell();
    }
    super.dispose();
  }

  void _showTapped(String label) {
    showDialog<void>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('$label tapped'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  Text _sectionTitle(String text) => Text(
    text.toUpperCase(),
    style: const TextStyle(
      color: Colors.white70,
      fontWeight: FontWeight.w700,
      fontSize: 13,
      letterSpacing: 1.4,
      shadows: [Shadow(color: Colors.black26, blurRadius: 6)],
    ),
  );

  Widget _controlButton(String label, VoidCallback onPressed) =>
      LiquidGlassButton(
        height: 44,
        padding: const EdgeInsets.symmetric(horizontal: 18),
        style: _darkStyle,
        onPressed: onPressed,
        child: Text(label, style: _buttonLabel.copyWith(fontSize: 14)),
      );

  /// Soft shadow keeps labels readable over any backdrop the glass samples.
  static const TextStyle _buttonLabel = TextStyle(
    color: Colors.white,
    fontWeight: FontWeight.w600,
    shadows: [
      Shadow(color: Colors.black38, blurRadius: 8, offset: Offset(0, 1)),
    ],
  );

  @override
  Widget build(BuildContext context) {
    final bottomInset = _usesNativeTabBar
        ? _theme.nativeShellBottomInset(context)
        : _theme.contentBottomInset(context);

    return Scaffold(
      extendBody: true,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Liquid Glass Kit'),
        backgroundColor: Colors.transparent,
        foregroundColor: Colors.white,
        titleTextStyle: const TextStyle(
          color: Colors.white,
          fontSize: 20,
          fontWeight: FontWeight.w700,
          shadows: [Shadow(color: Colors.black26, blurRadius: 8)],
        ),
      ),
      body: DecoratedBox(
        // Vivid backdrop with shapes: glass is transparent and only visible
        // over detailed content.
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Color(0xFF3A0CA3),
              Color(0xFF7209B7),
              Color(0xFFB5179E),
              Color(0xFF4CC9F0),
            ],
            stops: [0.0, 0.35, 0.65, 1.0],
          ),
        ),
        child: Stack(
          children: [
            const _BackdropShapes(),
            ListView(
              padding: EdgeInsets.fromLTRB(
                24,
                MediaQuery.paddingOf(context).top + 72,
                24,
                bottomInset,
              ),
              children: [
                Align(
                  alignment: Alignment.centerLeft,
                  child: LiquidGlassContainer(
                    borderRadius: 20,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 14,
                      vertical: 8,
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Container(
                          width: 8,
                          height: 8,
                          decoration: BoxDecoration(
                            shape: BoxShape.circle,
                            color: LiquidGlassPlatform.supportsNativeGlass
                                ? const Color(0xFF30D158)
                                : Colors.orangeAccent,
                          ),
                        ),
                        const SizedBox(width: 8),
                        Text(
                          LiquidGlassPlatform.supportsNativeGlass
                              ? 'Native glass · iOS '
                                    '${LiquidGlassPlatform.osMajorVersion} · '
                                    'Tab: ${_tabs[_currentIndex].title}'
                              : 'Plain fallback · '
                                    'Tab: ${_tabs[_currentIndex].title}',
                          style: _buttonLabel.copyWith(fontSize: 13),
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 32),
                _sectionTitle('Buttons'),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: [
                    LiquidGlassButton(
                      onPressed: () => _showTapped('Regular'),
                      child: const Text('Regular', style: _buttonLabel),
                    ),
                    LiquidGlassButton(
                      style: _clearStyle,
                      onPressed: () => _showTapped('Clear'),
                      child: const Text('Clear', style: _buttonLabel),
                    ),
                    LiquidGlassButton(
                      style: _tintedStyle,
                      onPressed: () => _showTapped('Tinted'),
                      child: const Text('Tinted', style: _buttonLabel),
                    ),
                    LiquidGlassButton(
                      style: _darkStyle,
                      onPressed: () => _showTapped('Dark'),
                      child: const Text('Dark', style: _buttonLabel),
                    ),
                    LiquidGlassButton(
                      style: const LiquidGlassStyle(nativeInteractive: false),
                      onPressed: () => _showTapped('No shimmer'),
                      child: const Text('No shimmer', style: _buttonLabel),
                    ),
                    const LiquidGlassButton(
                      onPressed: null,
                      child: Text('Disabled', style: _buttonLabel),
                    ),
                  ],
                ),
                const SizedBox(height: 28),
                _sectionTitle('Icon buttons'),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 14,
                  runSpacing: 14,
                  crossAxisAlignment: WrapCrossAlignment.center,
                  children: [
                    LiquidGlassIconButton(
                      icon: const Icon(Icons.favorite),
                      iconColor: Colors.white,
                      onPressed: () => _showTapped('Favorite'),
                    ),
                    LiquidGlassIconButton(
                      icon: const Icon(Icons.share),
                      iconColor: Colors.white,
                      style: _clearStyle,
                      onPressed: () => _showTapped('Share'),
                    ),
                    LiquidGlassIconButton(
                      icon: const Icon(Icons.add),
                      iconColor: Colors.white,
                      style: _tintedStyle,
                      size: 60,
                      iconSize: 28,
                      onPressed: () => _showTapped('Add'),
                    ),
                    LiquidGlassIconButton(
                      icon: const Icon(Icons.settings),
                      iconColor: Colors.white,
                      style: _darkStyle,
                      onPressed: () => _showTapped('Settings'),
                    ),
                  ],
                ),
                const SizedBox(height: 28),
                _sectionTitle('Icon + label'),
                const SizedBox(height: 12),
                LiquidGlassButton(
                  height: 56,
                  onPressed: () => _showTapped('Continue'),
                  child: const Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.arrow_forward, color: Colors.white, size: 20),
                      SizedBox(width: 8),
                      Text('Continue', style: _buttonLabel),
                    ],
                  ),
                ),
                if (_usesNativeTabBar) ...[
                  const SizedBox(height: 28),
                  _sectionTitle('Tab bar controls'),
                  const SizedBox(height: 12),
                  Wrap(
                    spacing: 12,
                    runSpacing: 12,
                    children: [
                      _controlButton(
                        _barVisible ? 'Hide bar' : 'Show bar',
                        () async {
                          setState(() => _barVisible = !_barVisible);
                          await _shell.setVisible(visible: _barVisible);
                        },
                      ),
                      _controlButton('Select Search', () async {
                        await _shell.setSelectedIndex(1);
                        setState(() => _currentIndex = 1);
                      }),
                      _controlButton(
                        'Clear selection',
                        () => _shell.clearSelectedIndex(),
                      ),
                      _controlButton(
                        _badgeOn ? 'Clear badge' : 'Set badge',
                        () async {
                          setState(() => _badgeOn = !_badgeOn);
                          await _shell.configureTabs(_tabs, theme: _theme);
                        },
                      ),
                      _controlButton('Open sheet', () {
                        _shell.presentModalBottomSheet<void>(
                          context: context,
                          backgroundColor: const Color(0xFF2A1458),
                          shape: const RoundedRectangleBorder(
                            borderRadius: BorderRadius.vertical(
                              top: Radius.circular(24),
                            ),
                          ),
                          builder: (context) => Padding(
                            padding: const EdgeInsets.all(32),
                            child: Column(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                const Text(
                                  'The native tab bar hides while this '
                                  'sheet is open and comes back when it '
                                  'closes.',
                                  textAlign: TextAlign.center,
                                  style: _buttonLabel,
                                ),
                                const SizedBox(height: 20),
                                LiquidGlassButton(
                                  style: _tintedStyle,
                                  onPressed: () =>
                                      Navigator.of(context).pop(),
                                  child: const Text(
                                    'Close',
                                    style: _buttonLabel,
                                  ),
                                ),
                                const SizedBox(height: 16),
                              ],
                            ),
                          ),
                        );
                      }),
                    ],
                  ),
                ],
              ],
            ),
          ],
        ),
      ),
      // Floats above the native tab bar via the package's FAB location.
      floatingActionButton: FloatingActionButton(
        backgroundColor: const Color(0xFF0A84FF),
        foregroundColor: Colors.white,
        onPressed: () => _showTapped('FAB'),
        child: const Icon(Icons.edit),
      ),
      floatingActionButtonLocation: _usesNativeTabBar
          ? const LiquidGlassNativeTabShellFabLocation()
          : FloatingActionButtonLocation.endFloat,
      // iOS 26+: Apple's real UITabBarController overlays this scaffold, so
      // no Flutter bar is rendered. Elsewhere: a plain Flutter bar.
      bottomNavigationBar: _usesNativeTabBar
          ? null
          : LiquidGlassBottomNav(
              items: _fallbackNavItems,
              currentIndex: _currentIndex,
              onIndexChanged: (index) => setState(() => _currentIndex = index),
            ),
    );
  }
}

/// Soft glowing orbs plus a couple of crisp accents behind the glass — the
/// material needs detail to refract, but the shapes stay out of the
/// content's way and blend with the gradient instead of fighting it.
class _BackdropShapes extends StatelessWidget {
  const _BackdropShapes();

  @override
  Widget build(BuildContext context) {
    Widget orb(double size, Color color, {double opacity = 0.55}) =>
        Container(
          width: size,
          height: size,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            gradient: RadialGradient(
              colors: [
                color.withValues(alpha: opacity),
                color.withValues(alpha: 0),
              ],
              stops: const [0.15, 1.0],
            ),
          ),
        );

    Widget ring(double size, Color color) => Container(
      width: size,
      height: size,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        border: Border.all(
          color: color.withValues(alpha: 0.8),
          width: 10,
        ),
      ),
    );

    return Positioned.fill(
      child: Stack(
        children: [
          Positioned(top: -60, left: -80, child: orb(320, Colors.white, opacity: 0.7)),
          Positioned(top: 260, right: -120, child: orb(320, const Color(0xFF4CC9F0), opacity: 0.9)),
          Positioned(top: 560, left: -100, child: orb(300, const Color(0xFFFF8FAB), opacity: 0.7)),
          Positioned(top: 820, right: -60, child: orb(260, const Color(0xFFFFD166), opacity: 0.6)),
          Positioned(top: 160, right: 60, child: ring(90, Colors.white)),
          Positioned(top: 470, left: 40, child: ring(60, const Color(0xFFFFD166))),
          Positioned(top: 700, right: 130, child: ring(70, const Color(0xFF80FFDB))),
        ],
      ),
    );
  }
}
1
likes
0
points
326
downloads

Publisher

unverified uploader

Weekly Downloads

Native iOS 26 Liquid Glass (UIGlassEffect and UITabBarController) for Flutter, with plain no-effect fallbacks on all other platforms.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on ios_liquid_glass

Packages that implement ios_liquid_glass