plump_icons 0.0.1+2 copy "plump_icons: ^0.0.1+2" to clipboard
plump_icons: ^0.0.1+2 copied to clipboard

Unofficial Flutter icon package for Streamline Plump Free icons, including Line and Solid styles.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:plump_icons/plump_icons.dart';
import 'package:plump_icons_example/generated_icon_list.dart';

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

enum StyleFilter { all, line, solid }

abstract final class _AppColors {
  static const blue = Color(0xFF2196F3);
  static const blueDark = Color(0xFF1976D2);
  static const blueLight = Color(0xFFE3F2FD);
  static const blueMuted = Color(0xFF64B5F6);
}

Future<void> _copyToClipboard(
  BuildContext context,
  ScaffoldMessengerState messenger,
  String text, {
  String message = 'Copied to clipboard',
}) async {
  await Clipboard.setData(ClipboardData(text: text));
  if (!context.mounted) {
    return;
  }

  final screenHeight = MediaQuery.sizeOf(context).height;
  final topInset = MediaQuery.paddingOf(context).top;

  messenger.clearSnackBars();
  messenger.showSnackBar(
    SnackBar(
      content: Text(message),
      behavior: SnackBarBehavior.floating,
      margin: EdgeInsets.only(
        bottom: screenHeight - topInset - 56,
        left: 16,
        right: 16,
      ),
      duration: const Duration(seconds: 2),
      dismissDirection: DismissDirection.up,
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Plump Icons',
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.light,
        scaffoldBackgroundColor: const Color(0xFFF5F5F7),
        colorScheme: ColorScheme.fromSeed(
          seedColor: _AppColors.blue,
          brightness: Brightness.light,
          surface: Colors.white,
        ),
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.white,
          foregroundColor: Color(0xFF1A1A1A),
          surfaceTintColor: Colors.white,
          elevation: 0,
        ),
        cardTheme: CardThemeData(
          color: Colors.white,
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
            side: const BorderSide(color: Color(0xFFEEEEEE)),
          ),
        ),
        segmentedButtonTheme: SegmentedButtonThemeData(
          style: ButtonStyle(
            visualDensity: VisualDensity.compact,
            tapTargetSize: MaterialTapTargetSize.shrinkWrap,
            elevation: const WidgetStatePropertyAll(0),
            shadowColor: const WidgetStatePropertyAll(Colors.transparent),
            backgroundColor: WidgetStateProperty.resolveWith((states) {
              if (states.contains(WidgetState.selected)) {
                return _AppColors.blue;
              }
              return _AppColors.blueLight;
            }),
            foregroundColor: WidgetStateProperty.resolveWith((states) {
              if (states.contains(WidgetState.selected)) {
                return Colors.white;
              }
              return _AppColors.blueDark;
            }),
            side: const WidgetStatePropertyAll(
              BorderSide(color: _AppColors.blue),
            ),
          ),
        ),
      ),
      home: const IconBrowserPage(),
    );
  }
}

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

  @override
  State<IconBrowserPage> createState() => _IconBrowserPageState();
}

class _IconBrowserPageState extends State<IconBrowserPage> {
  String _query = '';
  StyleFilter _styleFilter = StyleFilter.all;

  List<IconPreviewEntry> get _filteredEntries {
    final query = _query.trim().toLowerCase();
    var entries = iconPreviewEntries;

    if (query.isNotEmpty) {
      entries = entries
          .where((entry) => entry.name.toLowerCase().contains(query))
          .toList();
    }

    return entries.where((entry) {
      return switch (_styleFilter) {
        StyleFilter.all => entry.lineIcon != null || entry.solidIcon != null,
        StyleFilter.line => entry.lineIcon != null,
        StyleFilter.solid => entry.solidIcon != null,
      };
    }).toList();
  }

  void _showIconDetails(IconPreviewEntry entry) {
    final messenger = ScaffoldMessenger.of(context);

    showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.white,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (context) {
        final lineCode =
            entry.lineIcon == null ? null : 'PlumpIcons.${entry.name}Line';
        final solidCode =
            entry.solidIcon == null ? null : 'PlumpIcons.${entry.name}Solid';

        return Padding(
          padding: EdgeInsets.fromLTRB(
            24,
            16,
            24,
            24 + MediaQuery.paddingOf(context).bottom,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 40,
                height: 4,
                decoration: BoxDecoration(
                  color: const Color(0xFFE0E0E0),
                  borderRadius: BorderRadius.circular(2),
                ),
              ),
              const SizedBox(height: 20),
              Text(
                entry.name,
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                      fontWeight: FontWeight.w600,
                    ),
              ),
              const SizedBox(height: 24),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  if (entry.lineIcon != null)
                    _PreviewTile(
                      label: 'Line',
                      icon: entry.lineIcon!,
                      filled: false,
                    ),
                  if (entry.lineIcon != null && entry.solidIcon != null)
                    const SizedBox(width: 24),
                  if (entry.solidIcon != null)
                    _PreviewTile(
                      label: 'Solid',
                      icon: entry.solidIcon!,
                      filled: true,
                    ),
                ],
              ),
              const SizedBox(height: 24),
              if (lineCode != null) ...[
                _CodeBlock(
                  label: 'Line',
                  code: 'PlumpIcon($lineCode)',
                  messenger: messenger,
                ),
                const SizedBox(height: 12),
              ],
              if (solidCode != null)
                _CodeBlock(
                  label: 'Solid',
                  code: 'PlumpIcon($solidCode)',
                  messenger: messenger,
                ),
              if (lineCode != null && solidCode != null) ...[
                const SizedBox(height: 16),
                _BottomNavCodeBlock(
                  messenger: messenger,
                  code: 'BottomNavigationBarItem(\n'
                      '  icon: PlumpIcon($lineCode),\n'
                      '  activeIcon: PlumpIcon($solidCode),\n'
                      "  label: 'Home',\n"
                      ')',
                ),
              ],
            ],
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final entries = _filteredEntries;

    return Scaffold(
      backgroundColor: const Color(0xFFF5F5F7),
      body: SafeArea(
        child: Center(
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 1200),
            child: CustomScrollView(
              slivers: [
                SliverToBoxAdapter(
                  child: Padding(
                    padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    'Plump Icons',
                                    style: Theme.of(context)
                                        .textTheme
                                        .headlineSmall
                                        ?.copyWith(fontWeight: FontWeight.w700),
                                  ),
                                  const SizedBox(height: 4),
                                  Text(
                                    'Streamline Plump · Line & Solid',
                                    style: Theme.of(context)
                                        .textTheme
                                        .bodyMedium
                                        ?.copyWith(color: Colors.black54),
                                  ),
                                ],
                              ),
                            ),
                            SegmentedButton<StyleFilter>(
                              segments: const [
                                ButtonSegment(
                                  value: StyleFilter.all,
                                  label: Text('All'),
                                ),
                                ButtonSegment(
                                  value: StyleFilter.line,
                                  label: Text('Line'),
                                ),
                                ButtonSegment(
                                  value: StyleFilter.solid,
                                  label: Text('Solid'),
                                ),
                              ],
                              selected: {_styleFilter},
                              onSelectionChanged: (selection) {
                                setState(
                                  () => _styleFilter = selection.first,
                                );
                              },
                            ),
                          ],
                        ),
                        const SizedBox(height: 16),
                        SearchBar(
                          hintText: 'Search 500 icons...',
                          leading: const Icon(
                            Icons.search,
                            color: _AppColors.blueDark,
                          ),
                          elevation: const WidgetStatePropertyAll(0),
                          shadowColor:
                              const WidgetStatePropertyAll(Colors.transparent),
                          backgroundColor: const WidgetStatePropertyAll(
                            _AppColors.blueLight,
                          ),
                          side: const WidgetStatePropertyAll(
                            BorderSide(color: _AppColors.blue),
                          ),
                          hintStyle: WidgetStatePropertyAll(
                            Theme.of(context).textTheme.bodyMedium?.copyWith(
                                  color: _AppColors.blueMuted,
                                ),
                          ),
                          onChanged: (value) => setState(() => _query = value),
                          trailing: _query.isEmpty
                              ? null
                              : [
                                  IconButton(
                                    icon: const Icon(
                                      Icons.clear,
                                      color: _AppColors.blueDark,
                                    ),
                                    onPressed: () =>
                                        setState(() => _query = ''),
                                  ),
                                ],
                        ),
                        const SizedBox(height: 12),
                        Text(
                          '${entries.length} result${entries.length == 1 ? '' : 's'}',
                          style:
                              Theme.of(context).textTheme.bodySmall?.copyWith(
                                    color: Colors.black54,
                                  ),
                        ),
                        const SizedBox(height: 8),
                      ],
                    ),
                  ),
                ),
                if (entries.isEmpty)
                  const SliverFillRemaining(
                    hasScrollBody: false,
                    child: Center(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(Icons.search_off,
                              size: 48, color: Colors.black26),
                          SizedBox(height: 12),
                          Text('No icons match your search.'),
                        ],
                      ),
                    ),
                  )
                else
                  SliverPadding(
                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
                    sliver: SliverLayoutBuilder(
                      builder: (context, constraints) {
                        final width = constraints.crossAxisExtent;
                        final crossAxisCount = width >= 900
                            ? 5
                            : width >= 700
                                ? 4
                                : width >= 480
                                    ? 3
                                    : 2;

                        return SliverGrid(
                          gridDelegate:
                              SliverGridDelegateWithFixedCrossAxisCount(
                            crossAxisCount: crossAxisCount,
                            mainAxisSpacing: 12,
                            crossAxisSpacing: 12,
                            childAspectRatio: 0.92,
                          ),
                          delegate: SliverChildBuilderDelegate(
                            (context, index) {
                              final entry = entries[index];
                              return _IconCard(
                                entry: entry,
                                styleFilter: _styleFilter,
                                onTap: () => _showIconDetails(entry),
                              );
                            },
                            childCount: entries.length,
                          ),
                        );
                      },
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _IconCard extends StatelessWidget {
  const _IconCard({
    required this.entry,
    required this.styleFilter,
    required this.onTap,
  });

  final IconPreviewEntry entry;
  final StyleFilter styleFilter;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final showLine = styleFilter != StyleFilter.solid && entry.lineIcon != null;
    final showSolid =
        styleFilter != StyleFilter.line && entry.solidIcon != null;
    final singleMode = styleFilter != StyleFilter.all;

    return Material(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Ink(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: const Color(0xFFEEEEEE)),
          ),
          child: Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Expanded(
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      if (showLine)
                        Expanded(
                          child: _MiniIcon(
                            icon: entry.lineIcon!,
                            size: singleMode ? 36 : 28,
                          ),
                        ),
                      if (showLine && showSolid && !singleMode)
                        Container(
                          width: 1,
                          height: 32,
                          color: const Color(0xFFEEEEEE),
                        ),
                      if (showSolid)
                        Expanded(
                          child: _MiniIcon(
                            icon: entry.solidIcon!,
                            size: singleMode ? 36 : 28,
                            bold: true,
                          ),
                        ),
                    ],
                  ),
                ),
                Text(
                  entry.name,
                  textAlign: TextAlign.center,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                  style: Theme.of(context).textTheme.labelMedium?.copyWith(
                        fontWeight: FontWeight.w500,
                      ),
                ),
                if (!singleMode && showLine && showSolid) ...[
                  const SizedBox(height: 4),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      _StyleBadge(label: 'Line', filled: false),
                      const SizedBox(width: 6),
                      _StyleBadge(label: 'Solid', filled: true),
                    ],
                  ),
                ],
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _MiniIcon extends StatelessWidget {
  const _MiniIcon({
    required this.icon,
    required this.size,
    this.bold = false,
  });

  final PlumpIconData icon;
  final double size;
  final bool bold;

  @override
  Widget build(BuildContext context) {
    return PlumpIcon(
      icon,
      size: size,
      color: bold ? const Color(0xFF111111) : const Color(0xFF444444),
    );
  }
}

class _StyleBadge extends StatelessWidget {
  const _StyleBadge({required this.label, required this.filled});

  final String label;
  final bool filled;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
      decoration: BoxDecoration(
        color: filled ? const Color(0xFF1A1A1A) : Colors.transparent,
        borderRadius: BorderRadius.circular(4),
        border: Border.all(
          color: filled ? const Color(0xFF1A1A1A) : const Color(0xFFCCCCCC),
        ),
      ),
      child: Text(
        label,
        style: TextStyle(
          fontSize: 9,
          fontWeight: FontWeight.w600,
          color: filled ? Colors.white : const Color(0xFF666666),
        ),
      ),
    );
  }
}

class _PreviewTile extends StatelessWidget {
  const _PreviewTile({
    required this.label,
    required this.icon,
    required this.filled,
  });

  final String label;
  final PlumpIconData icon;
  final bool filled;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 96,
          height: 96,
          decoration: BoxDecoration(
            color: const Color(0xFFF8F8FA),
            borderRadius: BorderRadius.circular(16),
            border: Border.all(color: const Color(0xFFEEEEEE)),
          ),
          child: Center(
            child: PlumpIcon(
              icon,
              size: 48,
              color: filled ? const Color(0xFF111111) : const Color(0xFF444444),
            ),
          ),
        ),
        const SizedBox(height: 8),
        _StyleBadge(label: label, filled: filled),
      ],
    );
  }
}

class _CodeBlock extends StatelessWidget {
  const _CodeBlock({
    required this.label,
    required this.code,
    required this.messenger,
  });

  final String label;
  final String code;
  final ScaffoldMessengerState messenger;

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(
          width: 48,
          child: Text(
            label,
            style: Theme.of(context).textTheme.labelSmall?.copyWith(
                  color: Colors.black54,
                ),
          ),
        ),
        Expanded(
          child: _CopyableCodeFrame(
            code: code,
            messenger: messenger,
          ),
        ),
      ],
    );
  }
}

class _BottomNavCodeBlock extends StatelessWidget {
  const _BottomNavCodeBlock({
    required this.code,
    required this.messenger,
  });

  final String code;
  final ScaffoldMessengerState messenger;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Bottom bar',
          style: Theme.of(context).textTheme.labelMedium?.copyWith(
                fontWeight: FontWeight.w600,
              ),
        ),
        const SizedBox(height: 8),
        _CopyableCodeFrame(
          code: code,
          messenger: messenger,
          copyMessage: 'Bottom bar snippet copied',
          style: Theme.of(context).textTheme.bodySmall?.copyWith(
                fontFamily: 'monospace',
                height: 1.5,
              ),
        ),
      ],
    );
  }
}

class _CopyableCodeFrame extends StatelessWidget {
  const _CopyableCodeFrame({
    required this.code,
    required this.messenger,
    this.copyMessage,
    this.style,
  });

  final String code;
  final ScaffoldMessengerState messenger;
  final String? copyMessage;
  final TextStyle? style;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.fromLTRB(12, 8, 4, 8),
      decoration: BoxDecoration(
        color: const Color(0xFFF8F8FA),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: const Color(0xFFEEEEEE)),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Text(
              code,
              style: style ??
                  const TextStyle(
                    fontFamily: 'monospace',
                    fontSize: 12,
                  ),
            ),
          ),
          IconButton(
            tooltip: 'Copy',
            visualDensity: VisualDensity.compact,
            padding: EdgeInsets.zero,
            constraints: const BoxConstraints(
              minWidth: 32,
              minHeight: 32,
            ),
            icon: const Icon(Icons.copy, size: 16),
            onPressed: () => _copyToClipboard(
              context,
              messenger,
              code,
              message: copyMessage ?? 'Copied to clipboard',
            ),
          ),
        ],
      ),
    );
  }
}
2
likes
150
points
60
downloads
screenshot

Documentation

API reference

Publisher

verified publisherduocnguyen.dev

Weekly Downloads

Unofficial Flutter icon package for Streamline Plump Free icons, including Line and Solid styles.

Repository (GitHub)
View/report issues
Contributing

Topics

#icons #svg #vector-icons #flutter-icons

License

MIT (license)

Dependencies

flutter, flutter_svg, vector_graphics

More

Packages that depend on plump_icons