new_loading_indicator 4.0.0 copy "new_loading_indicator: ^4.0.0" to clipboard
new_loading_indicator: ^4.0.0 copied to clipboard

A fork of the loading_indicator package, providing a rich collection of loading animations for Flutter applications.

example/lib/main.dart

import 'package:material_ui/material_ui.dart';
import 'package:new_loading_indicator/new_loading_indicator.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Loading Indicators Demo',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
      home: const LoadingGallery(),
    );
  }
}

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

  @override
  State<LoadingGallery> createState() => _LoadingGalleryState();
}

class _LoadingGalleryState extends State<LoadingGallery> {
  /// The largest stroke width the slider offers. Its lowest position means
  /// "not set", so every indicator uses its default.
  static const double _maxStrokeWidth = 8;

  Indicator _selectedIndicator = Indicator.ballPulse;
  final List<Color> _colors = [Colors.blue];

  /// Drives the preview. It stays the same as the selection changes, so a
  /// paused preview stays paused when another indicator is picked.
  final LoadingIndicatorController _controller = LoadingIndicatorController();

  double _target = 0.5;
  double? _strokeWidth;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _togglePlayback() {
    switch (_controller.status) {
      case .playing || .pausing:
        _controller.pause();
      case .paused || .detached:
        _controller.resume();
    }
  }

  Future<void> _pauseAtTarget(LoadingIndicatorPauseBehavior behavior) async {
    try {
      await _controller.pauseAt(_target, behavior: behavior);
    } on LoadingIndicatorCommandCanceled {
      // A newer command replaced this one before the target was reached.
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Loading Indicators')),
      body: LayoutBuilder(
        builder: (context, constraints) {
          if (constraints.maxWidth > 600.0) {
            return _buildWideLayout();
          }

          return _buildNarrowLayout();
        },
      ),
    );
  }

  Widget _buildWideLayout() {
    return Row(
      children: [
        Expanded(flex: 2, child: _buildIndicatorList()),
        const VerticalDivider(),
        Expanded(flex: 3, child: _buildPreviewArea()),
      ],
    );
  }

  Widget _buildNarrowLayout() {
    return Column(
      children: [
        Expanded(flex: 3, child: _buildPreviewArea()),
        const Divider(),
        Expanded(flex: 2, child: _buildIndicatorList()),
      ],
    );
  }

  Widget _buildPreviewArea() {
    final TextTheme textTheme = Theme.of(context).textTheme;
    return Center(
      child: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 360.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                width: 150.0,
                height: 150.0,
                child: LoadingIndicator(
                  indicatorType: _selectedIndicator,
                  colors: _colors,
                  strokeWidth: _strokeWidth,
                  controller: _controller,
                ),
              ),
              const SizedBox(height: 24.0),
              Text(_selectedIndicator.name, style: textTheme.titleLarge),
              const SizedBox(height: 8.0),
              ListenableBuilder(
                listenable: _controller,
                builder: (context, _) => Text(
                  '${_controller.status.name} · '
                  '${_controller.progress?.toStringAsFixed(2) ?? '–'}',
                  key: const Key('playback-status'),
                  style: textTheme.bodyMedium,
                ),
              ),
              const SizedBox(height: 16.0),
              _buildPlaybackControls(),
              const SizedBox(height: 16.0),
              _buildSliders(textTheme),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildPlaybackControls() {
    final String target = _target.toStringAsFixed(2);
    return Wrap(
      alignment: WrapAlignment.center,
      crossAxisAlignment: WrapCrossAlignment.center,
      spacing: 8.0,
      runSpacing: 8.0,
      children: [
        ListenableBuilder(
          listenable: _controller,
          builder: (context, _) {
            final bool paused = switch (_controller.status) {
              .paused || .detached => true,
              .playing || .pausing => false,
            };
            return IconButton.filled(
              tooltip: paused ? 'Resume' : 'Pause',
              onPressed: _togglePlayback,
              icon: Icon(paused ? Icons.play_arrow : Icons.pause),
            );
          },
        ),
        FilledButton.tonal(
          onPressed: () => _pauseAtTarget(.waitUntilTarget),
          child: Text('Play to $target'),
        ),
        OutlinedButton(
          onPressed: () => _pauseAtTarget(.jumpToTarget),
          child: Text('Jump to $target'),
        ),
      ],
    );
  }

  Widget _buildSliders(TextTheme textTheme) {
    final double? strokeWidth = _strokeWidth;
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Text(
          'Target ${_target.toStringAsFixed(2)}',
          style: textTheme.labelLarge,
        ),
        Slider(
          key: const Key('target-slider'),
          value: _target,
          divisions: 20,
          label: _target.toStringAsFixed(2),
          onChanged: (value) => setState(() => _target = value),
        ),
        Text(
          strokeWidth == null
              ? 'Stroke width: default'
              : 'Stroke width ${strokeWidth.toStringAsFixed(0)}',
          style: textTheme.labelLarge,
        ),
        Slider(
          key: const Key('stroke-width-slider'),
          value: strokeWidth ?? 0,
          max: _maxStrokeWidth,
          divisions: _maxStrokeWidth.toInt(),
          label: strokeWidth?.toStringAsFixed(0) ?? 'default',
          onChanged: (value) =>
              setState(() => _strokeWidth = value == 0 ? null : value),
        ),
      ],
    );
  }

  Widget _buildIndicatorList() {
    return ListView.builder(
      itemCount: Indicator.values.length,
      itemBuilder: (context, index) {
        final indicator = Indicator.values[index];
        return ListTile(
          leading: SizedBox(
            width: 30.0,
            height: 30.0,
            child: LoadingIndicator(indicatorType: indicator, colors: _colors),
          ),
          title: Text(indicator.name),
          selected: _selectedIndicator == indicator,
          onTap: () => setState(() => _selectedIndicator = indicator),
        );
      },
    );
  }
}
12
likes
160
points
948
downloads

Documentation

API reference

Publisher

verified publisherbizjak.dev

Weekly Downloads

A fork of the loading_indicator package, providing a rich collection of loading animations for Flutter applications.

Repository (GitLab)
View/report issues

Topics

#loading-indicator

License

Apache-2.0 (license)

Dependencies

flutter, material_ui

More

Packages that depend on new_loading_indicator