Last active
February 27, 2026 08:37
-
-
Save JohanScheepers/e50377eac402d0f485c781a5a84511b2 to your computer and use it in GitHub Desktop.
no_material_flutter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/widgets.dart'; | |
| import 'dart:ui'; | |
| import 'package:signals/signals_flutter.dart'; | |
| /// Entry point of the application. | |
| void main() { | |
| runApp(const App()); | |
| } | |
| /// The root widget of the application. | |
| /// | |
| /// We use [WidgetsApp] instead of [MaterialApp] to maintain a completely | |
| /// custom design language without Material or Cupertino dependencies. | |
| class App extends StatelessWidget { | |
| const App({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return WidgetsApp( | |
| title: 'Flutter No Material & Cupertino', | |
| debugShowCheckedModeBanner: false, | |
| color: AppTheme.primary, | |
| // Minimalistic routing since we only have one main screen | |
| pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => | |
| PageRouteBuilder<T>( | |
| settings: settings, | |
| pageBuilder: (context, animation, secondaryAnimation) => | |
| builder(context), | |
| ), | |
| home: const CounterExample(), | |
| ); | |
| } | |
| } | |
| /// The main page of the application demonstrating the [Signals] counter | |
| /// and the high-fidelity [TechStackShowcase]. | |
| class CounterExample extends StatefulWidget { | |
| const CounterExample({super.key}); | |
| @override | |
| State<CounterExample> createState() => _CounterExampleState(); | |
| } | |
| /// State for [CounterExample] using [SignalsMixin] for reactive updates. | |
| class _CounterExampleState extends State<CounterExample> with SignalsMixin { | |
| // A reactive signal to hold the counter value. | |
| late final Signal<int> counter = createSignal(0); | |
| /// Increments the signal value, triggering an efficient UI rebuild. | |
| void _incrementCounter() { | |
| counter.value++; | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return ColoredBox( | |
| color: AppTheme.background, | |
| child: SafeArea( | |
| child: SingleChildScrollView( | |
| child: Column( | |
| children: [ | |
| // Custom Bar: Mimics an AppBar without Material dependencies. | |
| Container( | |
| height: AppTheme.appBarHeight, | |
| width: double.infinity, | |
| padding: AppTheme.appBarPadding, | |
| alignment: Alignment.centerLeft, | |
| child: const Text( | |
| 'Flutter Counter', | |
| style: AppTheme.appBarTitle, | |
| ), | |
| ), | |
| const SizedBox(height: 40), | |
| // Counter Section | |
| Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| const Text( | |
| 'You have pushed the button this many times:', | |
| style: AppTheme.bodyText, | |
| ), | |
| const SizedBox(height: 8), | |
| // Watch automatically rebuilds this widget when the signal changes. | |
| Watch( | |
| (context) => | |
| Text('${counter.value}', style: AppTheme.counterText), | |
| ), | |
| const SizedBox(height: 32), | |
| // Styled Increment Button | |
| GestureDetector( | |
| onTap: _incrementCounter, | |
| child: GlowContainer( | |
| size: null, // Makes the container wrap the child text | |
| borderRadius: AppTheme.buttonRadius, | |
| padding: AppTheme.buttonPadding, | |
| borderGradient: const LinearGradient( | |
| colors: [ | |
| Color(0xFF54E0E0), // Cyan start | |
| Color(0xFFE054E0), // Pink end | |
| ], | |
| begin: Alignment.topLeft, | |
| end: Alignment.bottomRight, | |
| ), | |
| child: const Text( | |
| 'Increment', | |
| style: AppTheme.buttonLabel, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| const SizedBox(height: 80), | |
| // Tech Stack Showcase: Horizontal bar with premium icons. | |
| const TechStackShowcase(), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// Central place for all colors, text styles, and spacing constants. | |
| final class AppTheme { | |
| // Brand Colors | |
| static const Color primary = Color(0xFF2196F3); | |
| static const Color background = Color(0xFF0F111A); // Deep Slate | |
| static const Color onPrimary = Color(0xFFFFFFFF); | |
| static const Color onBackground = Color(0xFFE0E0E0); | |
| // Typography - Note: decoration is mandatory in WidgetsApp context | |
| static const TextStyle appBarTitle = TextStyle( | |
| color: onPrimary, | |
| fontSize: 20, | |
| fontWeight: FontWeight.w600, | |
| decoration: TextDecoration.none, | |
| ); | |
| static const TextStyle bodyText = TextStyle( | |
| fontSize: 16, | |
| color: onBackground, | |
| decoration: TextDecoration.none, | |
| ); | |
| static const TextStyle counterText = TextStyle( | |
| fontSize: 48, | |
| fontWeight: FontWeight.bold, | |
| color: onBackground, | |
| decoration: TextDecoration.none, | |
| ); | |
| static const TextStyle buttonLabel = TextStyle( | |
| color: onPrimary, | |
| fontSize: 16, | |
| fontWeight: FontWeight.w600, | |
| decoration: TextDecoration.none, | |
| ); | |
| // Layout Spacing | |
| static const double appBarHeight = 56; | |
| static const EdgeInsets appBarPadding = EdgeInsets.symmetric(horizontal: 16); | |
| static const EdgeInsets buttonPadding = EdgeInsets.symmetric( | |
| horizontal: 24, | |
| vertical: 12, | |
| ); | |
| static const double buttonRadius = 8; | |
| } | |
| /// A horizontally scrolling bar that showcases technology icons | |
| /// with glassmorphism and glow effects. | |
| class TechStackShowcase extends StatelessWidget { | |
| const TechStackShowcase({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| // Shared gradient for icon borders to maintain visual consistency. | |
| const iconBorderGradient = LinearGradient( | |
| colors: [ | |
| Color(0xFF54E0E0), // Bright Cyan | |
| Color(0xFFE054E0), // Vibrant Magenta | |
| ], | |
| begin: Alignment.topLeft, | |
| end: Alignment.bottomRight, | |
| ); | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Stack( | |
| children: [ | |
| // Layer 1: Translucent bar background using low-opacity white. | |
| Positioned.fill( | |
| child: Padding( | |
| padding: const EdgeInsets.symmetric(vertical: 20.0), | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: const Color(0xFFFFFFFF).withValues(alpha: 0.05), | |
| border: Border.symmetric( | |
| horizontal: BorderSide( | |
| color: const Color(0xFFFFFFFF).withValues(alpha: 0.1), | |
| width: 1.0, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| // Layer 2: Centered icon row. | |
| Padding( | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 24.0, | |
| vertical: 32.0, | |
| ), | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| GlowContainer( | |
| borderGradient: iconBorderGradient, | |
| child: const FlutterLogo(size: 40), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| /// A specialized container that implements glassmorphism, | |
| /// gradient borders, and directional glow. | |
| class GlowContainer extends StatelessWidget { | |
| const GlowContainer({ | |
| super.key, | |
| required this.child, | |
| required this.borderGradient, | |
| this.innerBlur = 20.0, | |
| this.borderRadius = 16.0, | |
| this.padding = const EdgeInsets.all(12.0), | |
| this.size = 80.0, | |
| }); | |
| /// The widget to display inside the container. | |
| final Widget child; | |
| /// The gradient used for drawing the thin border. | |
| final Gradient borderGradient; | |
| /// The amount of backdrop blur for the frosted glass effect. | |
| final double innerBlur; | |
| /// The corner radius of the container. | |
| final double borderRadius; | |
| /// Internal padding for the child widget. | |
| final EdgeInsets padding; | |
| /// Optional fixed size. If null, the container shrink-wraps the child. | |
| final double? size; | |
| @override | |
| Widget build(BuildContext context) { | |
| final glowColors = borderGradient.colors; | |
| return Container( | |
| width: size, | |
| height: size, | |
| constraints: const BoxConstraints(), | |
| decoration: BoxDecoration( | |
| borderRadius: BorderRadius.circular(borderRadius), | |
| boxShadow: [ | |
| // Directional Glow 1: Cyan glow offset top-left | |
| BoxShadow( | |
| color: glowColors.first.withValues(alpha: 0.4), | |
| blurRadius: 20, | |
| spreadRadius: -2, | |
| offset: const Offset(-4, -4), | |
| ), | |
| // Directional Glow 2: Magenta glow offset bottom-right | |
| if (glowColors.length > 1) | |
| BoxShadow( | |
| color: glowColors.last.withValues(alpha: 0.4), | |
| blurRadius: 20, | |
| spreadRadius: -2, | |
| offset: const Offset(4, 4), | |
| ), | |
| // Layer 3: Subtle ambient glow for overall presence | |
| BoxShadow( | |
| color: glowColors.first.withValues(alpha: 0.1), | |
| blurRadius: 40, | |
| spreadRadius: 2, | |
| ), | |
| ], | |
| ), | |
| child: ClipRRect( | |
| borderRadius: BorderRadius.circular(borderRadius), | |
| child: BackdropFilter( | |
| // Frosted glass effect | |
| filter: ImageFilter.blur(sigmaX: innerBlur, sigmaY: innerBlur), | |
| child: CustomPaint( | |
| // Custom painter for the 1px gradient border | |
| painter: _GradientBorderPainter( | |
| gradient: borderGradient, | |
| radius: borderRadius, | |
| strokeWidth: 2.0, | |
| ), | |
| child: Container( | |
| padding: padding, | |
| decoration: BoxDecoration( | |
| borderRadius: BorderRadius.circular(borderRadius), | |
| // Increased transparency for the glass area around the icon | |
| gradient: LinearGradient( | |
| begin: Alignment.topLeft, | |
| end: Alignment.bottomRight, | |
| colors: [ | |
| const Color(0xFFFFFFFF).withValues(alpha: 0.05), | |
| const Color(0xFFFFFFFF).withValues(alpha: 0.02), | |
| ], | |
| ), | |
| ), | |
| // Center the child if a fixed size is provided, | |
| // otherwise let it shrink-wrap. | |
| child: size != null ? Center(child: child) : child, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// A custom painter that draws a rounded rectangle border with a gradient shader. | |
| class _GradientBorderPainter extends CustomPainter { | |
| _GradientBorderPainter({ | |
| required this.gradient, | |
| required this.radius, | |
| required this.strokeWidth, | |
| }); | |
| final Gradient gradient; | |
| final double radius; | |
| final double strokeWidth; | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| // Create the rect and shrink it by half the stroke width | |
| // to ensure the border is fully visible within the container bounds. | |
| final rect = Offset.zero & size; | |
| final RRect rrect = RRect.fromRectAndRadius( | |
| rect.deflate(strokeWidth / 2), | |
| Radius.circular(radius), | |
| ); | |
| final paint = Paint() | |
| ..style = PaintingStyle.stroke | |
| ..strokeWidth = strokeWidth | |
| ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 1.5) | |
| ..shader = gradient.createShader(rect); | |
| canvas.drawRRect(rrect, paint); | |
| } | |
| @override | |
| bool shouldRepaint(covariant _GradientBorderPainter oldDelegate) => | |
| oldDelegate.gradient != gradient || | |
| oldDelegate.radius != radius || | |
| oldDelegate.strokeWidth != strokeWidth; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment