s_standby 1.0.0 copy "s_standby: ^1.0.0" to clipboard
s_standby: ^1.0.0 copied to clipboard

A lightweight Flutter utility to show a progress overlay while a `Future` runs, with optional error/success builders.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 's_standby Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const StandbyHomePage(),
    );
  }
}

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

  @override
  State<StandbyHomePage> createState() => _StandbyHomePageState();
}

class _StandbyHomePageState extends State<StandbyHomePage> {
  bool _isBusy = false;
  bool _isDismissible = true;
  bool _announceTransitions = false;
  String _lastResult = 'No result yet';

  void _handleDismiss({
    bool? wasSuccessful,
    Object? error,
    StackTrace? stackTrace,
  }) {
    if (!mounted) return;

    String message;
    if (wasSuccessful == true) {
      message = 'Dismissed after success';
    } else if (wasSuccessful == false) {
      message = 'Dismissed after error: $error';
    } else {
      message = 'Dismissed before completion';
    }

    setState(() {
      _isBusy = false;
      _lastResult = message;
    });
  }

  Future<void> _runSuccessDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    final future = Future<void>.delayed(const Duration(seconds: 2));
    SStandby.show(
      isDismissible: _isDismissible,
      onDismissed: _handleDismiss,
      future: future,
      id: 'success_demo',
      title: 'Working on it…',
    );
  }

  Future<void> _runErrorDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    final future = Future<void>.delayed(const Duration(seconds: 2), () {
      throw Exception('Something went wrong');
    });

    SStandby.show<void>(
      future: future,
      onDismissed: _handleDismiss,
      id: 'error_demo',
      title: 'Processing…',
      isDismissible: _isDismissible,
    );
  }

  Future<void> _runCustomDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    final future = Future<void>.delayed(const Duration(seconds: 2), () {
      throw Exception('Custom error');
    });

    SStandby.show<void>(
      future: future,
      onDismissed: _handleDismiss,
      id: 'custom_demo',
      title: 'Customizing…',
      isDismissible: _isDismissible,
      dismissibleBarrierOpacity: 0.35,
      indicatorSize: 28,
      indicatorStrokeWidth: 4,
      indicatorColor: Colors.deepPurple,
      waitingDecoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.deepPurple, width: 1.2),
        boxShadow: const [
          BoxShadow(
            color: Color(0x22000000),
            blurRadius: 12,
            offset: Offset(0, 6),
          ),
        ],
      ),
      waitingPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
      errorDecoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.orange, width: 1.2),
      ),
      errorPadding: const EdgeInsets.all(16),
      errorBuilder: (context, error, stackTrace) {
        return DecoratedBox(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: Colors.orange, width: 1.2),
          ),
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.warning_amber, color: Colors.orange, size: 40),
                const SizedBox(height: 12),
                Text(
                  'Custom error: $error',
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 12),
                const Text(
                  'Tap outside to dismiss.',
                  style: TextStyle(color: Colors.black54),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Future<void> _runSuccessPopDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    final future = Future<void>.delayed(const Duration(seconds: 2));

    SStandby.show<void>(
      future: future,
      id: 'success_pop_demo',
      title: 'Saving…',
      isDismissible: _isDismissible,
      successDismissible: _isDismissible,
      onDismissed: _handleDismiss,
      successBuilder: (context) {
        return DecoratedBox(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: Colors.green, width: 1.2),
            boxShadow: const [
              BoxShadow(
                color: Color(0x22000000),
                blurRadius: 12,
                offset: Offset(0, 6),
              ),
            ],
          ),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.check_circle, color: Colors.green),
                const SizedBox(width: 12),
                Text(
                  'Saved successfully!',
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  void _dismissCurrent() {
    // For demo purposes we dismiss any known overlay ids.
    for (final id in <String>[
      'success_demo',
      'error_demo',
      'custom_demo',
      'success_pop_demo',
      'timeout_demo',
      'auto_success_demo',
    ]) {
      SStandby.dismiss(id);
    }
  }

  Future<void> _runTimeoutDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    SStandby.show<void>(
      id: 'timeout_demo',
      future: Future<void>.delayed(const Duration(seconds: 5)),
      timeout: const Duration(seconds: 1),
      isDismissible: _isDismissible,
      announceTransitions: _announceTransitions,
      semanticsLabelWaiting: 'Working…',
      semanticsLabelError: 'Timed out',
      onDismissed: _handleDismiss,
      timeoutBuilder: (context, error, stackTrace) {
        return DecoratedBox(
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: Colors.red, width: 1.2),
          ),
          child: const Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.timer_off, color: Colors.red, size: 40),
                SizedBox(height: 12),
                Text('Timed out (custom UI)'),
                SizedBox(height: 12),
                Text('Tap outside or use Dismiss button.'),
              ],
            ),
          ),
        );
      },
    );
  }

  Future<void> _runAutoDismissSuccessDemo() async {
    if (_isBusy) return;
    setState(() => _isBusy = true);

    SStandby.show<void>(
      id: 'auto_success_demo',
      future: Future<void>.delayed(const Duration(seconds: 2)),
      title: 'Finishing…',
      isDismissible: _isDismissible,
      announceTransitions: _announceTransitions,
      semanticsLabelWaiting: 'Finishing',
      semanticsLabelSuccess: 'Done',
      onDismissed: _handleDismiss,
      successBuilder: (context) {
        return DecoratedBox(
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: Colors.green, width: 1.2),
          ),
          child: const Padding(
            padding: EdgeInsets.all(16),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.check_circle, color: Colors.green),
                SizedBox(width: 12),
                Text('All set! (auto dismiss in 1s)'),
              ],
            ),
          ),
        );
      },
      successAutoDismissAfter: const Duration(seconds: 1),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('s_standby Example'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text(
                'Tap a button to show the overlay while a Future runs.',
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              SwitchListTile.adaptive(
                contentPadding: EdgeInsets.zero,
                title: const Text('Dismissible overlay'),
                subtitle: const Text('Allow tap outside to dismiss'),
                value: _isDismissible,
                onChanged: _isBusy
                    ? null
                    : (value) {
                        setState(() => _isDismissible = value);
                      },
              ),
              SwitchListTile.adaptive(
                contentPadding: EdgeInsets.zero,
                title: const Text('Announce transitions'),
                subtitle: const Text('Accessibility / screen reader friendly'),
                value: _announceTransitions,
                onChanged: _isBusy
                    ? null
                    : (value) {
                        setState(() => _announceTransitions = value);
                      },
              ),
              const SizedBox(height: 12),
              FilledButton(
                onPressed: _isBusy ? null : _runSuccessDemo,
                child: const Text('Run success demo'),
              ),
              const SizedBox(height: 12),
              OutlinedButton(
                onPressed: _isBusy ? null : _runErrorDemo,
                child: const Text('Run error demo'),
              ),
              const SizedBox(height: 12),
              ElevatedButton(
                onPressed: _isBusy ? null : _runCustomDemo,
                child: const Text('Run custom demo'),
              ),
              const SizedBox(height: 12),
              FilledButton.tonal(
                onPressed: _isBusy ? null : _runSuccessPopDemo,
                child: const Text('Run success pop demo'),
              ),
              const SizedBox(height: 12),
              OutlinedButton(
                onPressed: _isBusy ? null : _runAutoDismissSuccessDemo,
                child: const Text('Run auto-dismiss success demo'),
              ),
              const SizedBox(height: 12),
              ElevatedButton(
                onPressed: _isBusy ? null : _runTimeoutDemo,
                child: const Text('Run timeout demo'),
              ),
              const SizedBox(height: 12),
              FilledButton(
                onPressed: _isBusy ? _dismissCurrent : null,
                child: const Text('Dismiss overlay (programmatic)'),
              ),
              const SizedBox(height: 12),
              Text(
                _isBusy ? 'Overlay running…' : 'Idle',
                style: Theme.of(context).textTheme.bodyMedium,
              ),
              const SizedBox(height: 8),
              Text(
                _lastResult,
                textAlign: TextAlign.center,
                style: Theme.of(context)
                    .textTheme
                    .bodySmall
                    ?.copyWith(color: Colors.black54),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
0
points
96
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight Flutter utility to show a progress overlay while a `Future` runs, with optional error/success builders.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

async, flutter, pop_overlay, ticker_free_circular_progress_indicator

More

Packages that depend on s_standby