unicode_spinner 1.0.0
unicode_spinner: ^1.0.0 copied to clipboard
ASCII and Unicode animated spinners for Flutter. One widget, 25+ built-in frame sequences — braille (Claude-style), classic pipe, dots, arrows, block bars, moon phases, clock, emoji and more. Fully cu [...]
import 'package:flutter/material.dart';
import 'package:unicode_spinner/unicode_spinner.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'unicode_spinner',
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system,
home: const DemoPage(),
);
}
}
class _Section {
const _Section(this.label, this.spinners);
final String label;
final List<(String, UnicodeSpinner)> spinners;
}
class DemoPage extends StatelessWidget {
const DemoPage({super.key});
static const _mono = TextStyle(fontSize: 22, fontFamily: 'monospace');
static const _monoBig = TextStyle(fontSize: 28, fontFamily: 'monospace');
static const _emoji = TextStyle(fontSize: 24);
static final _sections = <_Section>[
_Section('Braille', [
('braille', UnicodeSpinner.braille(style: _mono)),
('brailleFull', UnicodeSpinner.brailleFull(style: _mono)),
('brailleQuarter', UnicodeSpinner.brailleQuarter(style: _mono)),
('snake', UnicodeSpinner.snake(style: _mono)),
]),
_Section('Classic ASCII', [
('line | / - \\', UnicodeSpinner.line(style: _monoBig)),
('pipe', UnicodeSpinner.pipe(style: _monoBig)),
]),
_Section('Dots', [
('dots', UnicodeSpinner.dots(style: _mono)),
('dotsBounce', UnicodeSpinner.dotsBounce(style: _mono)),
('dot', UnicodeSpinner.dot(style: _monoBig)),
]),
_Section('Geometric', [
('circle', UnicodeSpinner.circle(style: _monoBig)),
('triangle', UnicodeSpinner.triangle(style: _monoBig)),
('box', UnicodeSpinner.box(style: _monoBig)),
('square', UnicodeSpinner.square(style: _monoBig)),
('bowtie', UnicodeSpinner.bowtie(style: _monoBig)),
('hourglass', UnicodeSpinner.hourglass(style: _monoBig)),
]),
_Section('Arrows', [
('arrows', UnicodeSpinner.arrows(style: _monoBig)),
('arrowsBold', UnicodeSpinner.arrowsBold(style: _monoBig)),
]),
_Section('Bar / Block', [
('bar', UnicodeSpinner.bar(style: _monoBig)),
('grow', UnicodeSpinner.grow(style: _monoBig)),
('pulse', UnicodeSpinner.pulse(style: _monoBig)),
]),
_Section('Emoji', [
('moon', UnicodeSpinner.moon(style: _emoji)),
('clock', UnicodeSpinner.clock(style: _emoji)),
('earth', UnicodeSpinner.earth(style: _emoji)),
]),
_Section('Misc', [
('star', UnicodeSpinner.star(style: _monoBig)),
]),
];
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final sliverItems = <Widget>[];
for (final section in _sections) {
sliverItems.add(_SectionHeader(label: section.label));
for (final (label, spinner) in section.spinners) {
sliverItems.add(_SpinnerTile(label: label, spinner: spinner));
}
}
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
backgroundColor: cs.primary,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'unicode_spinner',
style: TextStyle(
fontFamily: 'monospace',
fontWeight: FontWeight.bold,
fontSize: 15,
color: cs.onPrimary,
),
),
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [cs.primary, cs.tertiary],
),
),
child: Align(
alignment: const Alignment(0, -0.25),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
UnicodeSpinner.braille(
style: TextStyle(fontSize: 52, color: cs.onPrimary),
),
const SizedBox(height: 6),
Text(
'25+ animated spinners ✦',
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: cs.onPrimary.withValues(alpha: 0.75),
letterSpacing: 0.5,
),
),
],
),
),
),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 32),
sliver: SliverList.list(children: sliverItems),
),
],
),
);
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader({required this.label});
final String label;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.fromLTRB(4, 28, 4, 6),
child: Row(
children: [
Text(
label.toUpperCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 1.4,
color: cs.primary,
fontFamily: 'monospace',
),
),
const SizedBox(width: 10),
Expanded(
child: Divider(
color: cs.outlineVariant,
height: 1,
thickness: 1,
),
),
],
),
);
}
}
class _SpinnerTile extends StatelessWidget {
const _SpinnerTile({required this.label, required this.spinner});
final String label;
final UnicodeSpinner spinner;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Material(
color: cs.surfaceContainerLow,
borderRadius: BorderRadius.circular(14),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
children: [
Expanded(
child: Text(
label,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 13,
color: cs.onSurfaceVariant,
),
),
),
spinner,
],
),
),
),
);
}
}