glossy_text 0.1.0
glossy_text: ^0.1.0 copied to clipboard
Glossy candy-style text for Flutter: outlined, gradient-filled glyphs with highlight bands, color-derived presets, and an animated shimmer sweep.
import 'package:flutter/material.dart';
import 'package:glossy_text/glossy_text.dart';
void main() => runApp(const GlossyTextDemoApp());
/// Rounded, chunky display style that suits the candy treatment.
TextStyle baloo(double size, {double weight = 800, double? letterSpacing}) {
return TextStyle(
fontFamily: 'Baloo 2',
fontSize: size,
height: 1,
letterSpacing: letterSpacing,
fontVariations: [FontVariation('wght', weight)],
);
}
class GlossyTextDemoApp extends StatelessWidget {
const GlossyTextDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'glossy_text demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFE6307D)),
scaffoldBackgroundColor: const Color(0xFFFFF6F9),
useMaterial3: true,
),
home: const ShowcasePage(),
);
}
}
class ShowcasePage extends StatelessWidget {
const ShowcasePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 48),
children: const [
_Hero(),
SizedBox(height: 40),
_SectionTitle('The original: streak counter'),
StreakDemo(),
SizedBox(height: 40),
_SectionTitle('Presets'),
PresetGallery(),
SizedBox(height: 40),
_SectionTitle('Make your own with GlossyTextEffect.candy()'),
CandyLab(),
SizedBox(height: 48),
],
),
);
}
}
class _Hero extends StatelessWidget {
const _Hero();
@override
Widget build(BuildContext context) {
return Column(
children: [
GlossyText(
'Glossy',
style: baloo(76, letterSpacing: 2),
effect: GlossyTextEffect.pink.copyWith(strokeWidth: 3.5),
shimmer: true,
),
const SizedBox(height: 8),
Text(
'candy-style text for Flutter',
style: baloo(18, weight: 600).copyWith(
color: const Color(0xFFB36A88),
),
),
],
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.title);
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
child: Text(
title,
textAlign: TextAlign.center,
style: baloo(15, weight: 700).copyWith(
color: const Color(0xFF9A8390),
letterSpacing: 0.4,
),
),
);
}
}
/// The treatment this package was extracted from: a big glossy streak
/// number above a small label.
class StreakDemo extends StatelessWidget {
const StreakDemo({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 56, vertical: 28),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(28),
boxShadow: const [
BoxShadow(
color: Color(0x14E6307D),
blurRadius: 24,
offset: Offset(0, 8),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GlossyText(
'128',
style: baloo(56, letterSpacing: 2),
semanticsLabel: '128 day streak',
),
const SizedBox(height: 10),
const Text(
'day streak',
style: TextStyle(fontSize: 14, color: Color(0xFFE6307D)),
),
],
),
),
);
}
}
class PresetGallery extends StatelessWidget {
const PresetGallery({super.key});
static final presets = <(String, GlossyTextEffect)>[
('pink', GlossyTextEffect.pink),
('gold', GlossyTextEffect.gold),
('silver', GlossyTextEffect.silver),
('grape', GlossyTextEffect.grape),
('ocean', GlossyTextEffect.ocean),
('lime', GlossyTextEffect.lime),
('tangerine', GlossyTextEffect.tangerine),
('mint', GlossyTextEffect.mint),
];
@override
Widget build(BuildContext context) {
return Wrap(
alignment: WrapAlignment.center,
spacing: 16,
runSpacing: 16,
children: [
for (final (name, effect) in presets)
Container(
width: 168,
padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
GlossyText('Candy', style: baloo(34), effect: effect),
const SizedBox(height: 8),
Text(
'.$name',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF9A8390),
),
),
],
),
),
],
);
}
}
/// Live editor: derive a full glossy effect from a single color, tweak the
/// stroke, toggle the shimmer.
class CandyLab extends StatefulWidget {
const CandyLab({super.key});
@override
State<CandyLab> createState() => _CandyLabState();
}
class _CandyLabState extends State<CandyLab> {
double _hue = 265;
double _strokeWidth = 3;
bool _shimmer = true;
Color get _base => HSLColor.fromAHSL(1, _hue, 0.78, 0.55).toColor();
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 360,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(28),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 96,
child: Center(
child: GlossyText(
'Sweet!',
style: baloo(56),
effect: GlossyTextEffect.candy(
_base,
strokeWidth: _strokeWidth,
),
shimmer: _shimmer,
),
),
),
const SizedBox(height: 8),
_LabeledSlider(
label: 'hue',
value: _hue,
max: 360,
activeColor: _base,
onChanged: (v) => setState(() => _hue = v),
),
_LabeledSlider(
label: 'stroke',
value: _strokeWidth,
max: 8,
activeColor: _base,
onChanged: (v) => setState(() => _strokeWidth = v),
),
SwitchListTile(
title: const Text('shimmer'),
dense: true,
contentPadding: EdgeInsets.zero,
activeTrackColor: _base,
value: _shimmer,
onChanged: (v) => setState(() => _shimmer = v),
),
],
),
),
);
}
}
class _LabeledSlider extends StatelessWidget {
const _LabeledSlider({
required this.label,
required this.value,
required this.max,
required this.activeColor,
required this.onChanged,
});
final String label;
final double value;
final double max;
final Color activeColor;
final ValueChanged<double> onChanged;
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
width: 56,
child: Text(label, style: const TextStyle(fontSize: 13)),
),
Expanded(
child: Slider(
value: value,
max: max,
activeColor: activeColor,
onChanged: onChanged,
),
),
],
);
}
}