Skip to main content

flutter_svga_easyplayer 0.1.0 copy "flutter_svga_easyplayer: ^0.1.0" to clipboard
flutter_svga_easyplayer: ^0.1.0 copied to clipboard

Play SVGA animations in Flutter with one widget: disk and memory caching, precaching, sound, typed errors and loading states built in.

example/lib/main.dart

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

/// The gift animations bundled in `assets/`.
const gifts = [
  'kiss',
  'green-hat',
  'corgi-cloud',
  'blue-rose-heart',
  'blessing-bag',
  'heart-jar',
  'flying-pig-hearts',
  'rocket-butterflies',
  'bunny-birthday-cake',
  'slipper-slap',
];

String assetOf(String gift) => 'assets/$gift.svga';

/// `corgi-cloud` → `Corgi cloud`
String titleOf(String gift) {
  final words = gift.replaceAll('-', ' ');
  return words[0].toUpperCase() + words.substring(1);
}

void main() => runApp(const GiftGalleryApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SVGA gift gallery',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        brightness: Brightness.dark,
      ),
      home: const GalleryScreen(),
    );
  }
}

/// A grid of every gift, each one playing on a loop.
class GalleryScreen extends StatelessWidget {
  const GalleryScreen({super.key});

  Future<void> _openUrl(BuildContext context) async {
    final controller = TextEditingController();
    final url = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Play from a URL'),
        content: TextField(
          controller: controller,
          autofocus: true,
          keyboardType: TextInputType.url,
          decoration: const InputDecoration(
            hintText: 'https://example.com/gift.svga',
          ),
          onSubmitted: (value) => Navigator.pop(context, value),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          FilledButton(
            onPressed: () => Navigator.pop(context, controller.text),
            child: const Text('Play'),
          ),
        ],
      ),
    );
    if (url == null || url.trim().isEmpty || !context.mounted) return;
    Navigator.push(
      context,
      MaterialPageRoute<void>(
        builder: (_) => GiftScreen(title: 'From URL', url: url.trim()),
      ),
    );
  }

  Future<void> _showCache(BuildContext context) async {
    final stats = await SVGACache.shared.getStats();
    if (!context.mounted) return;
    final megabytes = (stats['size'] as int) / (1024 * 1024);
    await showDialog<void>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Disk cache'),
        content: Text(
          '${stats['fileCount']} files, ${megabytes.toStringAsFixed(1)} MB\n'
          'In memory, idle: '
          '${(SVGAMemoryCache.shared.idleBytes / (1024 * 1024)).toStringAsFixed(1)} MB',
        ),
        actions: [
          TextButton(
            onPressed: () async {
              await SVGACache.shared.clear();
              SVGAMemoryCache.shared.clear();
              if (context.mounted) Navigator.pop(context);
            },
            child: const Text('Clear'),
          ),
          FilledButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gift gallery'),
        actions: [
          IconButton(
            tooltip: 'Play from a URL',
            icon: const Icon(Icons.link),
            onPressed: () => _openUrl(context),
          ),
          IconButton(
            tooltip: 'Cache',
            icon: const Icon(Icons.storage),
            onPressed: () => _showCache(context),
          ),
        ],
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(12),
        gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 220,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
          childAspectRatio: 0.72,
        ),
        itemCount: gifts.length,
        itemBuilder: (context, index) => _GiftTile(gift: gifts[index]),
      ),
    );
  }
}

class _GiftTile extends StatelessWidget {
  const _GiftTile({required this.gift});

  final String gift;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: InkWell(
        onTap: () => Navigator.push(
          context,
          MaterialPageRoute<void>(
            builder: (_) =>
                GiftScreen(title: titleOf(gift), asset: assetOf(gift)),
          ),
        ),
        child: Column(
          children: [
            Expanded(
              child: SVGAEasyPlayer(
                assetsName: assetOf(gift),
                isMute: true,
                allowDrawingOverflow: false,
                placeholder: const Center(child: CircularProgressIndicator()),
                errorBuilder: (_, _) =>
                    const Center(child: Icon(Icons.broken_image)),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: Text(titleOf(gift), overflow: TextOverflow.ellipsis),
            ),
          ],
        ),
      ),
    );
  }
}

/// One gift, full screen, with playback controls.
class GiftScreen extends StatefulWidget {
  const GiftScreen({super.key, required this.title, this.asset, this.url});

  final String title;
  final String? asset;
  final String? url;

  @override
  State<GiftScreen> createState() => _GiftScreenState();
}

class _GiftScreenState extends State<GiftScreen> {
  /// `null` loops forever, `0` plays once, `2` plays three times.
  int? _loops;
  bool _muted = false;

  /// Changes whenever playback should start over.
  int _run = 0;

  void _setLoops(int? loops) => setState(() {
    _loops = loops;
    _run++;
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          IconButton(
            tooltip: _muted ? 'Unmute' : 'Mute',
            icon: Icon(_muted ? Icons.volume_off : Icons.volume_up),
            onPressed: () => setState(() => _muted = !_muted),
          ),
        ],
      ),
      body: Center(
        child: SVGAEasyPlayer(
          key: ValueKey(_run),
          assetsName: widget.asset,
          resUrl: widget.url,
          loops: _loops,
          isMute: _muted,
          clearsAfterStop: false,
          placeholder: const CircularProgressIndicator(),
          errorBuilder: (context, error) => Padding(
            padding: const EdgeInsets.all(24),
            child: Text(switch (error) {
              SVGANetworkException(:final statusCode?) =>
                'The server answered with HTTP $statusCode.',
              SVGANetworkException() => 'Could not reach the server.',
              SVGATimeoutException() => 'The download took too long.',
              SVGAFormatException() => 'That is not an SVGA 2.x file.',
              _ => 'Could not load the animation.',
            }, textAlign: TextAlign.center),
          ),
          onFinished: () => ScaffoldMessenger.of(
            context,
          ).showSnackBar(const SnackBar(content: Text('Finished'))),
        ),
      ),
      bottomNavigationBar: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: SegmentedButton<int?>(
            segments: const [
              ButtonSegment(value: null, label: Text('Loop')),
              ButtonSegment(value: 0, label: Text('Once')),
              ButtonSegment(value: 2, label: Text('3 times')),
            ],
            selected: {_loops},
            onSelectionChanged: (selection) => _setLoops(selection.single),
          ),
        ),
      ),
    );
  }
}
4
likes
160
points
120
downloads

Documentation

API reference

Publisher

verified publisherzamansheikh.com

Weekly Downloads

Play SVGA animations in Flutter with one widget: disk and memory caching, precaching, sound, typed errors and loading states built in.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

archive, audioplayers, crypto, flutter, http, meta, path_provider

More

Packages that depend on flutter_svga_easyplayer