icon 2.2.1 copy "icon: ^2.2.1" to clipboard
icon: ^2.2.1 copied to clipboard

An extended Icon "too" for those that are not actually square, plus shadows support + neat Icon extension operators.

example/lib/main.dart

/// ## 🙋‍♂️ I'm an Icon Too!
/// This example is heavily (overly?) documented.
///
/// Hover over many terms in the code to see a description or explanation,
/// and [`Ctrl`]+[`Click`] to goto the definition (in most editors; maybe [`F12`]).
library icon_example;

import 'dart:math' show Random;
import 'dart:ui' show ImageFilter;
import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome;
import 'package:flutter/material.dart';

import 'package:icon/icon.dart';
import 'custom_icons.dart';
import 'icon_list.dart';
import 'demo3.dart';

/// The non-square icon's presentation height
/// for this example, utilized in the FABs and TabBar.
const ICON_SIZE = 20.0;

/// Title for this example in the AppBar.
const TITLE = 'Icon Example: Using an IconToo';

/// This [IconData] represents the state of Demo 2 being hidden
/// and is an open icon.
const DEMO2_IS_CLOSED = Icons.note_add_outlined;

/// This [IconData] represents the state of Demo 2 being shown
/// and is a close icon.
const DEMO2_IS_OPEN = Icons.cancel_outlined;

/// Constant for UI styling.
const DURATION = Duration(milliseconds: 500);

/// Constant for UI styling.
const CURVE = Curves.fastOutSlowIn;

void main() => runApp(IconExample());

/// This class is a [StatelessWidget] that, when built,
/// returns a [MaterialApp] with given properties.
///
/// The [MaterialApp.home] field defines the home page, [Landing].
class IconExample extends StatelessWidget {
  Widget build(BuildContext context) => MaterialApp(
        // debugShowCheckedModeBanner: false,
        title: TITLE,
        home: const Landing(),
        themeMode: ThemeMode.dark,
        darkTheme: ThemeData.from(
          colorScheme: ColorScheme.fromSwatch(
            primarySwatch: Colors.red,
            backgroundColor: const Color(0xFF550000),
          ),
        ).copyWith(textTheme: ThemeData(brightness: Brightness.dark).textTheme),
      );
}

/// This class is a [StatefulWidget] that creates a [State],
/// [LandingState], when Flutter requests it.
class Landing extends StatefulWidget {
  const Landing({Key? key}) : super(key: key);
  LandingState createState() => LandingState();
}

/// This class is a [State] with [Type] of [Landing],
/// `with` the mixin [TickerProviderStateMixin]
/// in order to accommodate a [TabBar] for its [Scaffold].
///
/// Like a [MaterialApp], a [Scaffold] is used less frequently than
/// other styling [Widgets]--generally once per "page" or "view".
class LandingState extends State<Landing> with TickerProviderStateMixin {
  /// These TextStyles are initialized `late`r, once this `LandingState` is built.
  late TextStyle technical, overline, headline4, headline1;
  // These doubles are provided the width and height of the screen `late`r.
  late double width, height;

  /// The AppBar leading IconButton toggles the second demo.
  /// Initialize the IconData value for the button here.
  ///
  /// We will control other stateful UI elements with this value.
  IconData demo2Icon = DEMO2_IS_CLOSED;

  /// Because this is a sample app...
  int _counter = 0;

  /// Because this is a sample app...
  void incrementCounter() => setState(() => _counter++);

  Widget build(BuildContext context) {
    // Force portrait orientation.
    SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
    // Store display resolution.
    width = MediaQuery.of(context).size.width;
    height = MediaQuery.of(context).size.height;

    /// Shortcut some `TextStyle`s that originate from `Theme.of`
    overline = Theme.of(context).textTheme.overline!.copyWith(shadows: SHADOWS);
    technical = Theme.of(context).textTheme.subtitle2!.copyWith(
          fontFamily: 'serif',
          fontWeight: FontWeight.bold,
          shadows: SHADOWS,
          fontSize: 10,
        );
    headline1 = Theme.of(context).textTheme.headline1!.copyWith(
          shadows: SHADOWS,
          color: const Color(0xFFFFFFFF),
        );
    headline4 = Theme.of(context).textTheme.headline4!.copyWith(
          shadows: SHADOWS,
          color: const Color(0xFFFFFFFF),
        );

    /// Return this built `LandingState`.
    return Scaffold(
      body: Stack(children: [
        buildBackground(),
        buildBody(),
        maybeBuildDemo2(), // This build methods checks the value of `demo2Icon`.
      ]),

      appBar: AppBar(
        centerTitle: true,
        title: const Text(TITLE, style: TextStyle(shadows: SHADOWS)),
        elevation: (demo2Icon == DEMO2_IS_CLOSED) ? 0 : 50,

        // Tapping the AppBar leading button will toggle the IconData for
        // `demo2Icon` which is used to toggle visibility of Demo 2.
        leading: IconButton(
          // This IconToo with default params would be visually equivalent
          // to a standard Icon, but elect to pass 👥 `shadows`.
          icon: IconToo(demo2Icon, shadows: SHADOWS),
          tooltip: 'Toggle Demo 2: Wrapping Icons',
          onPressed: () => setState(() => demo2Icon =
              // If it is one IconData, make it the other.
              (demo2Icon == DEMO2_IS_OPEN) ? DEMO2_IS_CLOSED : DEMO2_IS_OPEN),
        ),

        actions: [
          IconButton(
            icon: IconToo(demo2Icon, shadows: SHADOWS),
            tooltip: 'Toggle Demo 2: Wrapping Icons',
            onPressed: () => Navigator.push(
                context, MaterialPageRoute(builder: (_) => const Demo3())),
          )
        ],

        bottom: TabBar(
          controller: TabController(vsync: this, length: 1),
          tabs: [
            Tab(
              icon: IconToo(
                CustomIcons.logo_bugbash,
                sizeX: ICON_SIZE * ICON_WIDTH_RATIO,
                sizeY: ICON_SIZE,
                shadows: SHADOWS,
              ),
            )
          ],
        ),
      ),

      // 🔤 Floating Action Buttons demonstrating the alignment and
      // sizing issues of non-square IconData in traditional Icons.
      floatingActionButton: AnimatedOpacity(
        duration: DURATION,
        curve: CURVE,
        opacity: (demo2Icon == DEMO2_IS_OPEN) ? 0 : 1,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            // 🔘 `Icon` for `IconButton` with shortest-side `iconSize`
            // * Broken.
            buildFAB(
              label: 'Icon 🔻\nIconButton\nShortest Side',
              textStyle: technical,
              child: buildIconButton(
                icon: const Icon(
                  CustomIcons.logo_bugbash,
                  size: ICON_SIZE,
                  color: Colors.white,
                ),
                // Provide shortest-side [iconSize]; result is left-edge of
                // icon is centered in a circle too small to fit anyway.
                iconSize: ICON_SIZE,
                tooltip:
                    'Typical experience with non-square Icons in IconButtons',
              ),
            ),

            // 🔘 `Icon` for `IconButton` with longest-side `iconSize`
            // * Broken.
            buildFAB(
              label: 'Icon 🔻\nIconButton\nLongest Side',
              textStyle: technical,
              child: buildIconButton(
                icon: const Icon(
                  CustomIcons.logo_bugbash,
                  size: ICON_SIZE,
                  color: Colors.white,
                ),
                // Provide longest-side [iconSize]; result is left-edge of
                // icon is centered in a circle wide enough to fit.
                iconSize: ICON_SIZE * ICON_WIDTH_RATIO,
                tooltip:
                    'IconButton passed an iconSize larger than Icon, but problem persists',
              ),
            ),

            // 🔘 `IconToo` for `IconButton` with longest-side `iconSize`
            // * Works!.
            buildFAB(
              label: 'IconToo 🔻\nIconButton\nLongest Side',
              textStyle: technical,
              child: buildIconButton(
                icon: const IconToo(
                  CustomIcons.logo_bugbash,
                  sizeX: ICON_SIZE * ICON_WIDTH_RATIO, sizeY: ICON_SIZE,
                  color: Colors.white,
                  shadows: SHADOWS, // 👥 `shadows` bonus!
                ),
                // Provide longest-side [iconSize]; result is
                // centered icon in a circle wide enough to fit.
                iconSize: ICON_SIZE * ICON_WIDTH_RATIO,
                tooltip:
                    'IconToo extends Icon but provides a trueSize SizedBox',
              ),
            ),
          ],
        ),
      ),
    );
  }

  /// 🔘 Set-up [IconButton] with matching parameters.
  ///
  /// Builds a black [Container] whose `child`
  /// is an [IconButton] with provided [icon], [iconSize], and [tooltip],
  /// that `onPressed` calls [incrementCounter].
  Widget buildIconButton({
    required Icon icon,
    required double iconSize,
    required String tooltip,
  }) =>
      Container(
        decoration: const BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.black38,
        ),
        child: IconButton(
          icon: icon,
          iconSize: iconSize,
          tooltip: tooltip,
          onPressed: incrementCounter,
        ),
      );

  /// 🔤 Builds a short [Column] requiring a
  /// [label] styled by [textStyle] and a [child]; which, in the case
  /// of this example, will be [buildIconButton].
  ///
  /// Ignore touch input with [IgnorePointer] if 2️⃣ Demo2 is open.
  Widget buildFAB({
    required String label,
    required TextStyle textStyle,
    required Widget child,
  }) =>
      IgnorePointer(
        ignoring: (demo2Icon == DEMO2_IS_OPEN),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              label,
              textAlign: TextAlign.center,
              style: textStyle,
            ),
            child,
          ],
        ),
      );

  /// 👕 Returns the content on the main page of this example application.
  ///
  /// Will be obscured if 2️⃣ Demo2 is open.
  Padding buildBody() => Padding(
        padding: const EdgeInsets.all(25.0),
        child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              RichText(
                text: TextSpan(
                  text: 'A nice, basic counter example\n'.toUpperCase(),
                  style: overline,
                  children: [
                    TextSpan(
                      text: 'Number of ',
                      style: headline4,
                    ),
                    TextSpan(
                      text: '~',
                      style: headline4
                          .merge(TextStyle(fontFamily: 'BashinBugs-Stripped')),
                    ),
                    TextSpan(text: '\nbutton presses:', style: headline4),
                  ],
                ),
              ),
              AnimatedContainer(
                duration: DURATION,
                curve: Curves.easeInExpo,
                padding: const EdgeInsets.all(25.0),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(100),
                  color: Theme.of(context).primaryColor.withOpacity(0.25),
                  backgroundBlendMode: BlendMode.difference,
                ),
                child: Text(
                  (_counter < 10)
                      ? '0' + _counter.toString()
                      : _counter.toString(),
                  style: headline1,
                ),
              ),
              Spacer(),
              Flexible(
                child: Text(
                  'ICON_SIZE = $ICON_SIZE\nICON_WIDTH_RATIO = $ICON_WIDTH_RATIO',
                  style: technical.copyWith(fontSize: 24),
                ),
              ),
              Flexible(
                child: Text(
                  'The IconData used in this example, [CustomIcons.logo_bugbash], is four times wider than it is tall. Literally, the width of the glyph is 4000 while others typically have a width of 1000.',
                  style: overline.copyWith(
                    letterSpacing: -0.25,
                    fontSize: 18,
                    color: const Color(0xDDFFFFFF),
                  ),
                ),
              ),
            ]),
      );

  /// 2️⃣ Sizes Demo 2 into view according to value of [demo2Icon].
  ///
  /// TODO: Make Demo2 a separate `class`.
  Widget maybeBuildDemo2() {
    // Icon Key #id, increments with every Icon
    var i = 0;

    // This method will be used twice, once for `Icon`s and again for `IconToo`s.
    Expanded buildDemo2Wrap({required List<Icon> children}) =>
        Expanded(flex: 5, child: Wrap(children: children));

    // A set-up RichText, ready for a single `String`.
    Expanded buildDemo2Header({required String technicalText}) => Expanded(
          flex: 2,
          child: RichText(
            textAlign: TextAlign.center,
            text: TextSpan(
              text: 'Wrapping ',
              style: headline4,
              children: [
                TextSpan(
                  text: technicalText,
                  style: technical.copyWith(
                    fontSize: headline4.fontSize,
                  ),
                ),
                const TextSpan(text: 's\n'),
                TextSpan(
                  text: 'with intermittent non-square IconData',
                  style: headline4.copyWith(fontSize: 16),
                )
              ],
            ),
          ),
        );

    // An aligned Material surface with InkWell and BackdropFilter.
    Widget buildDemo2Body({required Widget child}) => Align(
          alignment: Alignment.topCenter,
          child: BackdropFilter(
            filter: (demo2Icon == DEMO2_IS_OPEN)
                ? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
                : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
            child: Material(
              color: Theme.of(context).backgroundColor.withOpacity(0.5),
              animationDuration: DURATION,
              elevation: 50,
              child: InkWell(
                highlightColor: Colors.transparent,
                splashColor: Theme.of(context).primaryColor.withOpacity(0.75),
                onTap: () => setState(() {}),
                child: AnimatedContainer(
                  width: width,
                  height: (demo2Icon == DEMO2_IS_OPEN) ? height : 0,
                  duration: DURATION,
                  curve: CURVE,
                  padding: const EdgeInsets.all(25),
                  child: child,
                ),
              ),
            ),
          ),
        );

    /// Return the built 2️⃣ Demo2.
    return buildDemo2Body(
      child: Column(
        children: (demo2Icon == DEMO2_IS_CLOSED)
            ? const []
            : <Widget>[
                /// 🛑 Wrapping [Icon]s
                buildDemo2Header(technicalText: 'Icon'),
                buildDemo2Wrap(
                  children: <Icon>[
                    for (IconData icon in ICONS)
                      Icon(
                        icon,
                        size: 50,
                        key: Key('Icon#${i++}:$icon:'),
                        semanticLabel: 'an Icon from Flutter',
                        // Random MaterialColor with 50% opacity to show overlap
                        color: Colors
                            .primaries[Random().nextInt(Colors.accents.length)]
                            .withOpacity(0.5),
                      )
                  ],
                ),

                /// 🆕 Wrapping [IconToo]s
                buildDemo2Header(technicalText: 'IconToo'),
                buildDemo2Wrap(
                  // [IconToo]s extend [Icon]
                  children: <Icon>[
                    for (IconData icon in ICONS)
                      IconToo(
                        icon,
                        sizeX: (icon == CustomIcons.logo_bugbash)
                            ? 50 * ICON_WIDTH_RATIO
                            : 50,
                        sizeY: 50,
                        key: Key('IconToo#${i++}:$icon:'),
                        semanticLabel: 'an IconToo from package Icon',
                        // Random MaterialColor with 50% opacity to show overlap
                        color: Colors
                            .primaries[Random().nextInt(Colors.accents.length)]
                            .withOpacity(0.5),
                        // 👥 Yay, shadows! This List is `const` which saves memory.
                        shadows: const [
                          Shadow(
                            color: Colors.black38,
                            blurRadius: 0,
                            offset: Offset(1, 1),
                          ),
                        ],
                      )
                  ],
                ),

                // Tip about `shadows` support
                Flexible(
                  child: Text(
                    '👥 Plus custom shadows!\nTap \'n hold to preview with a brighter background.',
                    textAlign: TextAlign.center,
                    style:
                        overline.copyWith(letterSpacing: 0.6, shadows: SHADOWS),
                  ),
                ),
              ],
      ),
    );
  }

  /// 🌆 Loads a nice space picture from [APOD](https://apod.nasa.gov 'NASA APOD'),
  /// and when it has, fades this image in, fit to cover the display.
  Image buildBackground() => Image.network(
        'https://apod.nasa.gov/apod/image/2101/NGC1316Center_HubbleNobre_2585.jpg',
        // Stretch the photo to the size of the app.
        fit: BoxFit.cover,
        width: width,
        height: height,

        // This frameBuilder fades in the photo when it loads.
        //
        // A static picture has `1` frame, but `null` if it is not loaded.
        // (An animated GIF would have more than one frame, as an example.)
        frameBuilder: (_, child, frame, wasLoaded) {
          if (wasLoaded) return child; // Return picture if loaded, otherwise:
          return AnimatedOpacity(
            child: child,
            opacity: frame == null ? 0 : 1, // `frame==null` before loading
            duration: DURATION, // Fade from 0->1 `opacity` over this duration
            curve: CURVE, // Curves describe the "shape" or "how" of animation
          );
        },
      );
}
11
likes
130
pub points
82%
popularity

Publisher

verified publisherzaba.app

An extended Icon "too" for those that are not actually square, plus shadows support + neat Icon extension operators.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (LICENSE)

Dependencies

flutter

More

Packages that depend on icon