import 'dart:async'; import 'package:flutter/material.dart'; import 'package:holo_shiny/holo_shiny.dart'; /// Entry point for the package demo application. void main() { runApp(const ExampleApp()); } /// Root app widget for the interactive shader demo. class ExampleApp extends StatelessWidget { const ExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const ExampleHome(), ); } } /// Interactive screen used to test style and uniform controls. class ExampleHome extends StatefulWidget { const ExampleHome({super.key}); @override State createState() => _ExampleHomeState(); } class _ExampleHomeState extends State { late final ShinyController _sensorController; late final StreamController _externalTiltController; late final ShinyController _overrideController; double _prismatic = 0.8; double _sparkle = 0.8; double _specular = 0.8; double _diffraction = 0.8; double _opacity = 1.0; ShinyProfile _profile = ShinyProfile.crackedIce; @override void initState() { super.initState(); _sensorController = ShinyController(useSensor: true); _externalTiltController = StreamController.broadcast(); _overrideController = ShinyController(tiltStream: _externalTiltController.stream); } @override void dispose() { _sensorController.dispose(); _overrideController.dispose(); _externalTiltController.close(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('holo_shiny example'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text('Shiny on Any Widget'), const SizedBox(height: 8), Center( child: SizedBox( width: 320, height: 120, child: Shiny( controller: _sensorController, profile: _profile, prismatic: _prismatic, sparkle: _sparkle, specular: _specular, diffraction: _diffraction, opacity: _opacity, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF18263E), borderRadius: BorderRadius.circular(14), ), child: const Row( children: [ Icon(Icons.auto_awesome, color: Colors.white), SizedBox(width: 12), Expanded( child: Text( 'This can wrap any widget, including app chrome.', style: TextStyle(color: Colors.white), ), ), ], ), ), ), ), ), const SizedBox(height: 24), const Text('ShinyCard (shape + rotation + shine)'), const SizedBox(height: 8), Center( child: ShinyCard( controller: _sensorController, profile: _profile, background: const _DemoCardBackground(), foreground: const _RareBadge(), prismatic: _prismatic, sparkle: _sparkle, specular: _specular, diffraction: _diffraction, opacity: _opacity, ), ), const SizedBox(height: 16), _StylePicker( selectedProfile: _profile, onChanged: (ShinyProfile profile) { setState(() { _profile = profile; }); }, ), const SizedBox(height: 8), _LabeledSlider('Prismatic', _prismatic, (double value) { setState(() { _prismatic = value; }); }), _LabeledSlider('Sparkle', _sparkle, (double value) { setState(() { _sparkle = value; }); }), _LabeledSlider('Specular', _specular, (double value) { setState(() { _specular = value; }); }), _LabeledSlider('Diffraction', _diffraction, (double value) { setState(() { _diffraction = value; }); }), _LabeledSlider('Opacity', _opacity, (double value) { setState(() { _opacity = value; }); }), const SizedBox(height: 24), const Text('External Tilt Stream + Custom Shape (card)'), const SizedBox(height: 8), _TiltPresetPicker( onChanged: (Offset tilt) { _externalTiltController.add(tilt); }, ), const SizedBox(height: 8), Center( child: ShinyCard( controller: _overrideController, profile: _profile, prismatic: _prismatic, sparkle: _sparkle, specular: _specular, diffraction: _diffraction, opacity: _opacity, shape: const StadiumBorder(), background: Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF1A2A44), Color(0xFF15263D)], ), ), ), foreground: const Center( child: Text( 'EXTERNAL STREAM', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w900, letterSpacing: 1.2, ), ), ), ), ), ], ), ), ); } } class _LabeledSlider extends StatelessWidget { /// Creates a slider row with a live numeric label. const _LabeledSlider(this.label, this.value, this.onChanged); final String label; final double value; final ValueChanged onChanged; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('$label: ${value.toStringAsFixed(2)}'), Slider( value: value, divisions: 20, label: value.toStringAsFixed(2), onChanged: onChanged, ), ], ); } } class _StylePicker extends StatelessWidget { const _StylePicker({ required this.selectedProfile, required this.onChanged, }); final ShinyProfile selectedProfile; final ValueChanged onChanged; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Profile'), const SizedBox(height: 6), SegmentedButton( segments: ShinyProfile.values .map((ShinyProfile profile) => ButtonSegment( value: profile, label: Text(_profileLabel(profile)), )) .toList(), selected: {selectedProfile}, onSelectionChanged: (Set value) { if (value.isNotEmpty) { onChanged(value.first); } }, showSelectedIcon: false, multiSelectionEnabled: false, ), ], ); } String _profileLabel(ShinyProfile profile) { switch (profile) { case ShinyProfile.holographicSilver: return 'Holographic Silver'; case ShinyProfile.crackedIce: return 'Cracked Ice'; case ShinyProfile.silverMosaic: return 'Silver Mosaic'; case ShinyProfile.superGoldVinyl: return 'Super Gold Vinyl'; } } } /// Circular D-pad control for feeding the external tilt stream. class _TiltPresetPicker extends StatelessWidget { const _TiltPresetPicker({required this.onChanged}); final ValueChanged onChanged; static const double _tiltAmount = 0.7; static const double _deadZoneRatio = 0.18; void _emitDragTilt(Offset localPosition, Size size) { final Offset center = Offset(size.width / 2.0, size.height / 2.0); final double radius = size.shortestSide / 2.0; final Offset delta = localPosition - center; final double distance = delta.distance; if (radius <= 0 || distance <= radius * _deadZoneRatio) { onChanged(Offset.zero); return; } Offset normalized = delta / radius; final double normalizedDistance = normalized.distance; if (normalizedDistance > 1.0) { normalized = normalized / normalizedDistance; } onChanged(normalized * _tiltAmount); } @override Widget build(BuildContext context) { return Center( child: SizedBox( width: 176, height: 176, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Size size = constraints.biggest; return GestureDetector( behavior: HitTestBehavior.opaque, onPanDown: (DragDownDetails details) { _emitDragTilt(details.localPosition, size); }, onPanUpdate: (DragUpdateDetails details) { _emitDragTilt(details.localPosition, size); }, onPanEnd: (_) => onChanged(Offset.zero), onPanCancel: () => onChanged(Offset.zero), child: DecoratedBox( decoration: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).colorScheme.surfaceContainerHighest, ), child: Stack( children: [ Align( alignment: Alignment.topCenter, child: IconButton.filledTonal( tooltip: 'Tilt Up', onPressed: () => onChanged(const Offset(0.0, -_tiltAmount)), icon: const Icon(Icons.keyboard_arrow_up), ), ), Align( alignment: Alignment.centerLeft, child: IconButton.filledTonal( tooltip: 'Tilt Left', onPressed: () => onChanged(const Offset(-_tiltAmount, 0.0)), icon: const Icon(Icons.keyboard_arrow_left), ), ), Align( alignment: Alignment.center, child: IconButton.outlined( tooltip: 'Center Tilt', onPressed: () => onChanged(Offset.zero), icon: const Icon(Icons.adjust), ), ), Align( alignment: Alignment.centerRight, child: IconButton.filledTonal( tooltip: 'Tilt Right', onPressed: () => onChanged(const Offset(_tiltAmount, 0.0)), icon: const Icon(Icons.keyboard_arrow_right), ), ), Align( alignment: Alignment.bottomCenter, child: IconButton.filledTonal( tooltip: 'Tilt Down', onPressed: () => onChanged(const Offset(0.0, _tiltAmount)), icon: const Icon(Icons.keyboard_arrow_down), ), ), ], ), ), ); }, ), ), ); } } /// Demonstration card background for the primary example. class _DemoCardBackground extends StatelessWidget { const _DemoCardBackground(); @override Widget build(BuildContext context) { return Stack( fit: StackFit.expand, children: [ Image.asset( 'assets/pokemon.png', fit: BoxFit.cover, alignment: Alignment.topCenter, errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { return Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF2D1B1B), Color(0xFF120D18)], ), ), padding: const EdgeInsets.all(16), child: const Align( alignment: Alignment.bottomLeft, child: Text( 'Demo Card', style: TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w900, ), ), ), ); }, ), DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withValues(alpha: 0.10), Colors.black.withValues(alpha: 0.22), ], ), ), ), ], ); } } /// Small badge overlay placed on top of the shiny card. class _RareBadge extends StatelessWidget { const _RareBadge(); @override Widget build(BuildContext context) { return Align( alignment: Alignment.topRight, child: Container( margin: const EdgeInsets.all(12), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(99), ), child: const Text( 'RARE', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w800, letterSpacing: 1, ), ), ), ); } }