Last active
April 15, 2026 17:16
-
-
Save Slozzyondul/58c9c812af9d3464a68dd66e1f2d41cd to your computer and use it in GitHub Desktop.
gym2.dart
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/material.dart'; | |
| import 'package:google_fonts/google_fonts.dart'; | |
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
| class VoltColors { | |
| static const Color surface = Color(0xFF131313); | |
| static const Color surfaceContainerLow = Color(0xFF1C1B1B); | |
| static const Color surfaceContainer = Color(0xFF201F1F); | |
| static const Color surfaceContainerHighest = Color(0xFF353534); | |
| static const Color surfaceContainerHigh = Color(0xFF2B2B2B); | |
| static const Color surfaceContainerLowest = Color(0xFF131313); | |
| static const Color primary = Color(0xFFFFFFFF); | |
| static const Color primaryFixedDim = Color(0xFFA6D700); // Electric Lime | |
| static const Color secondary = Color( | |
| 0xFFFF5722, | |
| ); // Bold Orange (approximation) | |
| static const Color onSurfaceVariant = Color(0xFFC3CAAC); | |
| // Light Mode Colors | |
| static const Color surfaceLight = Color(0xFFF5F5F5); | |
| static const Color surfaceContainerLowLight = Color(0xFFFFFFFF); | |
| static const Color onSurfaceLight = Color(0xFF131313); | |
| static const LinearGradient primaryGradient = LinearGradient( | |
| colors: [primary, primaryFixedDim], | |
| begin: Alignment.topLeft, | |
| end: Alignment.bottomRight, | |
| stops: [0.0, 1.0], | |
| transform: GradientRotation(2.356), // 135 degrees | |
| ); | |
| } | |
| final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.dark); | |
| class VoltTheme { | |
| static ThemeData lightTheme = ThemeData( | |
| useMaterial3: true, | |
| brightness: Brightness.light, | |
| scaffoldBackgroundColor: VoltColors.surfaceLight, | |
| colorScheme: const ColorScheme.light( | |
| surface: VoltColors.surfaceLight, | |
| primary: VoltColors.primaryFixedDim, | |
| secondary: VoltColors.secondary, | |
| onSurface: VoltColors.onSurfaceLight, | |
| ), | |
| textTheme: GoogleFonts.lexendTextTheme( | |
| const TextTheme( | |
| displayLarge: TextStyle( | |
| fontSize: 56.0, | |
| fontWeight: FontWeight.bold, | |
| color: VoltColors.onSurfaceLight, | |
| letterSpacing: -1.12, | |
| height: 1.1, | |
| ), | |
| displayMedium: TextStyle( | |
| fontSize: 44.0, | |
| fontWeight: FontWeight.bold, | |
| color: VoltColors.onSurfaceLight, | |
| letterSpacing: -0.88, | |
| height: 1.1, | |
| ), | |
| headlineLarge: TextStyle( | |
| fontSize: 32.0, | |
| fontWeight: FontWeight.bold, | |
| color: VoltColors.onSurfaceLight, | |
| ), | |
| titleLarge: TextStyle( | |
| fontSize: 22.0, | |
| fontWeight: FontWeight.w600, | |
| color: VoltColors.onSurfaceLight, | |
| ), | |
| bodyLarge: TextStyle(fontSize: 16.0, color: VoltColors.onSurfaceLight), | |
| bodyMedium: TextStyle(fontSize: 14.0, color: Colors.black87), | |
| labelMedium: TextStyle( | |
| fontSize: 12.0, | |
| fontWeight: FontWeight.bold, | |
| letterSpacing: 0.6, | |
| ), | |
| ), | |
| ), | |
| ); | |
| static ThemeData darkTheme = ThemeData( | |
| useMaterial3: true, | |
| brightness: Brightness.dark, | |
| scaffoldBackgroundColor: VoltColors.surface, | |
| colorScheme: const ColorScheme.dark( | |
| surface: VoltColors.surface, | |
| primary: VoltColors.primaryFixedDim, | |
| secondary: VoltColors.secondary, | |
| onSurfaceVariant: VoltColors.onSurfaceVariant, | |
| ), | |
| textTheme: GoogleFonts.lexendTextTheme( | |
| const TextTheme( | |
| displayLarge: TextStyle( | |
| fontSize: 56.0, // 3.5rem | |
| fontWeight: FontWeight.bold, | |
| letterSpacing: -1.12, // -2% | |
| height: 1.1, | |
| ), | |
| displayMedium: TextStyle( | |
| fontSize: 44.0, // 2.75rem | |
| fontWeight: FontWeight.bold, | |
| letterSpacing: -0.88, // -2% | |
| height: 1.1, | |
| ), | |
| headlineLarge: TextStyle(fontSize: 32.0, fontWeight: FontWeight.bold), | |
| titleLarge: TextStyle(fontSize: 22.0, fontWeight: FontWeight.w600), | |
| bodyLarge: TextStyle(fontSize: 16.0, color: Colors.white), | |
| bodyMedium: TextStyle( | |
| fontSize: 14.0, | |
| color: VoltColors.onSurfaceVariant, | |
| ), | |
| labelMedium: TextStyle( | |
| fontSize: 12.0, | |
| fontWeight: FontWeight.bold, | |
| letterSpacing: 0.6, // 5% | |
| ), | |
| ), | |
| ), | |
| appBarTheme: const AppBarTheme( | |
| backgroundColor: Colors.transparent, | |
| elevation: 0, | |
| ), | |
| ); | |
| } | |
| import 'package:flutter/material.dart'; | |
| import '../theme.dart'; | |
| class VoltCard extends StatefulWidget { | |
| final Widget child; | |
| final VoidCallback? onTap; | |
| final double? width; | |
| final double? height; | |
| final Color? color; | |
| final EdgeInsets? padding; | |
| const VoltCard({ | |
| super.key, | |
| required this.child, | |
| this.onTap, | |
| this.width, | |
| this.height, | |
| this.color, | |
| this.padding, | |
| }); | |
| @override | |
| State<VoltCard> createState() => _VoltCardState(); | |
| } | |
| class _VoltCardState extends State<VoltCard> { | |
| bool _isHovered = false; | |
| @override | |
| Widget build(BuildContext context) { | |
| return MouseRegion( | |
| onEnter: (_) => setState(() => _isHovered = true), | |
| onExit: (_) => setState(() => _isHovered = false), | |
| child: GestureDetector( | |
| onTap: widget.onTap, | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 300), | |
| curve: Curves.easeOutCubic, | |
| width: widget.width, | |
| height: widget.height, | |
| padding: widget.padding ?? const EdgeInsets.all(24), | |
| transform: Matrix4.translationValues( | |
| 0.0, | |
| _isHovered ? -8.0 : 0.0, | |
| 0.0, | |
| ), | |
| decoration: BoxDecoration( | |
| color: widget.color ?? Theme.of(context).colorScheme.surfaceContainerLow, | |
| borderRadius: BorderRadius.circular(32), | |
| border: Border.all( | |
| color: _isHovered | |
| ? VoltColors.primaryFixedDim | |
| : Theme.of(context).colorScheme.surfaceContainerHighest, | |
| width: 1.5, | |
| ), | |
| boxShadow: [ | |
| if (_isHovered) | |
| BoxShadow( | |
| color: VoltColors.primaryFixedDim.withValues(alpha: 0.15), | |
| blurRadius: 24, | |
| spreadRadius: 2, | |
| ), | |
| ], | |
| ), | |
| child: widget.child, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class VoltServiceCard extends StatelessWidget { | |
| final String title; | |
| final String category; | |
| final String? description; | |
| final String? imageUrl; | |
| final double scale; | |
| final VoidCallback? onTap; | |
| const VoltServiceCard({ | |
| super.key, | |
| required this.title, | |
| required this.category, | |
| this.description, | |
| this.imageUrl, | |
| required this.scale, | |
| this.onTap, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return VoltCard( | |
| onTap: onTap, | |
| width: 280 * scale, | |
| padding: EdgeInsets.zero, | |
| child: ClipRRect( | |
| borderRadius: BorderRadius.circular(32), | |
| child: Stack( | |
| children: [ | |
| if (imageUrl != null) | |
| Positioned.fill( | |
| child: Image.asset( | |
| imageUrl!, | |
| fit: BoxFit.cover, | |
| errorBuilder: (_, _, _) => | |
| Container(color: VoltColors.surfaceContainerHigh), | |
| ), | |
| ), | |
| Positioned.fill( | |
| child: Container( | |
| decoration: BoxDecoration( | |
| gradient: LinearGradient( | |
| begin: Alignment.topCenter, | |
| end: Alignment.bottomCenter, | |
| colors: [ | |
| Colors.transparent, | |
| Colors.black.withValues(alpha: 0.8), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| Padding( | |
| padding: const EdgeInsets.all(24.0), | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Container( | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 12, | |
| vertical: 6, | |
| ), | |
| decoration: BoxDecoration( | |
| color: VoltColors.surfaceContainerHigh.withValues( | |
| alpha: 0.8, | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Text( | |
| category.toUpperCase(), | |
| style: VoltTheme.darkTheme.textTheme.labelSmall | |
| ?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| letterSpacing: 2, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| const Icon( | |
| Icons.bolt, | |
| color: VoltColors.primaryFixedDim, | |
| size: 20, | |
| ), | |
| ], | |
| ), | |
| const Spacer(), | |
| Text( | |
| title, | |
| style: VoltTheme.darkTheme.textTheme.headlineSmall | |
| ?.copyWith( | |
| fontSize: 24 * scale, | |
| color: Colors.white, | |
| letterSpacing: 1, | |
| ), | |
| ), | |
| if (description != null) ...[ | |
| const SizedBox(height: 12), | |
| Text( | |
| description!, | |
| maxLines: 2, | |
| overflow: TextOverflow.ellipsis, | |
| style: VoltTheme.darkTheme.textTheme.bodyMedium?.copyWith( | |
| color: Colors.white70, | |
| fontSize: 14 * scale, | |
| ), | |
| ), | |
| ], | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class VoltProductCard extends StatelessWidget { | |
| final String title; | |
| final String price; | |
| final String? imageUrl; | |
| final bool isNew; | |
| final double scale; | |
| final VoidCallback? onTap; | |
| const VoltProductCard({ | |
| super.key, | |
| required this.title, | |
| required this.price, | |
| this.imageUrl, | |
| this.isNew = false, | |
| required this.scale, | |
| this.onTap, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return VoltCard( | |
| onTap: onTap, | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Container( | |
| height: 140 * scale, | |
| width: double.infinity, | |
| decoration: BoxDecoration( | |
| color: VoltColors.surfaceContainerHigh.withValues(alpha: 0.5), | |
| borderRadius: BorderRadius.circular(20), | |
| ), | |
| child: ClipRRect( | |
| borderRadius: BorderRadius.circular(20), | |
| child: imageUrl != null | |
| ? Image.asset( | |
| imageUrl!, | |
| fit: BoxFit.cover, | |
| errorBuilder: (_, _, _) => const Icon( | |
| Icons.shopping_bag_outlined, | |
| color: VoltColors.primaryFixedDim, | |
| size: 48, | |
| ), | |
| ) | |
| : const Icon( | |
| Icons.shopping_bag_outlined, | |
| color: VoltColors.primaryFixedDim, | |
| size: 48, | |
| ), | |
| ), | |
| ), | |
| const SizedBox(height: 24), | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Expanded( | |
| child: Text( | |
| title, | |
| style: VoltTheme.darkTheme.textTheme.titleLarge?.copyWith( | |
| fontSize: 20 * scale, | |
| color: Colors.white, | |
| ), | |
| ), | |
| ), | |
| if (isNew) | |
| Container( | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 8, | |
| vertical: 4, | |
| ), | |
| decoration: BoxDecoration( | |
| color: VoltColors.primaryFixedDim, | |
| borderRadius: BorderRadius.circular(8), | |
| ), | |
| child: const Text( | |
| 'NEW', | |
| style: TextStyle( | |
| color: Colors.black, | |
| fontSize: 10, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| const SizedBox(height: 12), | |
| Text( | |
| price, | |
| style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| fontSize: 18 * scale, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class VoltActionCard extends StatelessWidget { | |
| final String title; | |
| final IconData icon; | |
| final String subtitle; | |
| final double scale; | |
| final VoidCallback? onTap; | |
| const VoltActionCard({ | |
| super.key, | |
| required this.title, | |
| required this.icon, | |
| required this.subtitle, | |
| required this.scale, | |
| this.onTap, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return VoltCard( | |
| onTap: onTap, | |
| child: Row( | |
| children: [ | |
| Container( | |
| padding: const EdgeInsets.all(16), | |
| decoration: BoxDecoration( | |
| color: VoltColors.primaryFixedDim.withValues(alpha: 0.1), | |
| shape: BoxShape.circle, | |
| ), | |
| child: Icon( | |
| icon, | |
| color: VoltColors.primaryFixedDim, | |
| size: 28 * scale, | |
| ), | |
| ), | |
| const SizedBox(width: 24), | |
| Expanded( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| title, | |
| style: VoltTheme.darkTheme.textTheme.titleLarge?.copyWith( | |
| fontSize: 20 * scale, | |
| color: Colors.white, | |
| ), | |
| ), | |
| const SizedBox(height: 4), | |
| Text( | |
| subtitle, | |
| style: VoltTheme.darkTheme.textTheme.bodyMedium?.copyWith( | |
| color: VoltColors.onSurfaceVariant.withValues(alpha: 0.6), | |
| fontSize: 14 * scale, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| const Icon(Icons.arrow_forward_ios, color: Colors.white24, size: 16), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| import 'dart:ui'; | |
| import 'package:flutter/material.dart'; | |
| class VoltGlassBox extends StatelessWidget { | |
| final Widget child; | |
| final double borderRadius; | |
| final double blur; | |
| final Color? color; | |
| final double opacity; | |
| final double? height; | |
| final double? width; | |
| final EdgeInsetsGeometry? padding; | |
| const VoltGlassBox({ | |
| super.key, | |
| required this.child, | |
| this.borderRadius = 40.0, | |
| this.blur = 20.0, | |
| this.color, | |
| this.opacity = 0.6, | |
| this.height, | |
| this.width, | |
| this.padding, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return ClipRRect( | |
| borderRadius: BorderRadius.circular(borderRadius), | |
| child: BackdropFilter( | |
| filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), | |
| child: Container( | |
| width: width, | |
| height: height, | |
| padding: padding, | |
| decoration: BoxDecoration( | |
| color: (color ?? Colors.grey.shade900).withValues(alpha: opacity), | |
| borderRadius: BorderRadius.circular(borderRadius), | |
| ), | |
| child: child, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| import '../theme.dart'; | |
| class VoltLogo extends StatelessWidget { | |
| final double scale; | |
| const VoltLogo({super.key, this.scale = 1.0}); | |
| @override | |
| Widget build(BuildContext context) { | |
| final theme = Theme.of(context); | |
| final isDark = theme.brightness == Brightness.dark; | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| 'VOLT', | |
| style: theme.textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| letterSpacing: 8.0 * scale, | |
| fontSize: 10.0 * scale, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| Text( | |
| 'KINETIC', | |
| style: theme.textTheme.labelMedium?.copyWith( | |
| color: isDark ? Colors.white : Colors.black, | |
| letterSpacing: 2.0 * scale, | |
| fontSize: 10.0 * scale, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
| import '../theme.dart'; | |
| import 'volt_glass_box.dart'; | |
| class VoltNavBar extends ConsumerWidget { | |
| final int activeIndex; | |
| final Function(int) onIndexChanged; | |
| const VoltNavBar({ | |
| super.key, | |
| required this.activeIndex, | |
| required this.onIndexChanged, | |
| }); | |
| @override | |
| Widget build(BuildContext context, WidgetRef ref) { | |
| final screenWidth = MediaQuery.of(context).size.width; | |
| final isLargeScreen = screenWidth > 800; | |
| final themeMode = ref.watch(themeModeProvider); | |
| final barWidth = isLargeScreen ? 650.0 : double.infinity; | |
| return Center( | |
| child: ConstrainedBox( | |
| constraints: BoxConstraints(maxWidth: barWidth), | |
| child: Padding( | |
| padding: const EdgeInsets.fromLTRB(24, 10, 24, 20), | |
| child: VoltGlassBox( | |
| height: 70, | |
| color: Theme.of(context).colorScheme.surfaceContainerHigh, | |
| padding: const EdgeInsets.symmetric(horizontal: 16), | |
| child: Row( | |
| mainAxisAlignment: isLargeScreen ? MainAxisAlignment.spaceEvenly : MainAxisAlignment.spaceBetween, | |
| children: [ | |
| _buildNavItem(context, Icons.home_filled, 'HOME', 0), | |
| _buildNavItem(context, Icons.fitness_center, 'SERVICES', 1), | |
| _buildNavItem(context, Icons.shopping_bag, 'SHOP', 2), | |
| _buildNavItem(context, Icons.alternate_email, 'CONTACT', 3), | |
| _buildNavItem(context, Icons.admin_panel_settings, 'ADMIN', 4), | |
| IconButton( | |
| onPressed: () { | |
| ref.read(themeModeProvider.notifier).state = | |
| themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark; | |
| }, | |
| icon: Icon( | |
| themeMode == ThemeMode.dark ? Icons.light_mode : Icons.dark_mode, | |
| color: VoltColors.primaryFixedDim, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildNavItem(BuildContext context, IconData icon, String label, int index) { | |
| final active = activeIndex == index; | |
| final isDark = Theme.of(context).brightness == Brightness.dark; | |
| return GestureDetector( | |
| onTap: () => onIndexChanged(index), | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 300), | |
| padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), | |
| decoration: BoxDecoration( | |
| color: active ? VoltColors.primaryFixedDim.withValues(alpha: 0.1) : Colors.transparent, | |
| borderRadius: BorderRadius.circular(20), | |
| ), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| Icon( | |
| icon, | |
| color: active ? VoltColors.primaryFixedDim : (isDark ? Colors.white24 : Colors.black26), | |
| size: 20, | |
| ), | |
| const SizedBox(height: 4), | |
| Text( | |
| label, | |
| style: Theme.of(context).textTheme.labelSmall?.copyWith( | |
| color: active ? VoltColors.primaryFixedDim : (isDark ? Colors.white24 : Colors.black26), | |
| fontSize: 8, | |
| fontWeight: active ? FontWeight.bold : FontWeight.normal, | |
| letterSpacing: 1.0, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| class VoltResponsive { | |
| static bool isMobile(BuildContext context) => | |
| MediaQuery.of(context).size.width < 800; | |
| static bool isTablet(BuildContext context) => | |
| MediaQuery.of(context).size.width >= 800 && | |
| MediaQuery.of(context).size.width < 1400; | |
| static bool isDesktop(BuildContext context) => | |
| MediaQuery.of(context).size.width >= 1400; | |
| static double width(BuildContext context) => MediaQuery.of(context).size.width; | |
| static double height(BuildContext context) => MediaQuery.of(context).size.height; | |
| /// Global scaling factor for layout and fonts. | |
| /// We cap this on larger screens to prevent UI elements from becoming overly large. | |
| static double scale(BuildContext context) { | |
| final w = width(context); | |
| if (w > 1600) return 1.1; // Reduced from 1.2 to keep elements balanced | |
| if (w > 1000) return 1.05; | |
| if (w < 400) return 0.9; // Scale down for very narrow mobile | |
| return 1.0; | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| import '../theme.dart'; | |
| class VoltSectionHeader extends StatelessWidget { | |
| final String title; | |
| final String? actionText; | |
| final VoidCallback? onActionTap; | |
| const VoltSectionHeader({ | |
| super.key, | |
| required this.title, | |
| this.actionText, | |
| this.onActionTap, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Text( | |
| title, | |
| style: VoltTheme.darkTheme.textTheme.labelMedium, | |
| ), | |
| if (actionText != null) | |
| GestureDetector( | |
| onTap: onActionTap, | |
| child: Padding( | |
| padding: const EdgeInsets.only(right: 24.0), | |
| child: Text( | |
| actionText!, | |
| style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| // screens | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; | |
| import '../theme.dart'; | |
| import '../api_service.dart'; | |
| import '../widgets/volt_responsive.dart'; | |
| import '../widgets/volt_logo.dart'; | |
| import '../widgets/volt_card.dart'; | |
| class ServicesScreen extends StatefulWidget { | |
| const ServicesScreen({super.key}); | |
| @override | |
| State<ServicesScreen> createState() => _ServicesScreenState(); | |
| } | |
| class _ServicesScreenState extends State<ServicesScreen> { | |
| List<dynamic>? _services; | |
| bool _isLoading = true; | |
| String _selectedCategory = 'ALL'; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _loadData(); | |
| } | |
| Future<void> _loadData() async { | |
| try { | |
| final services = await ApiService.getServices(); | |
| if (mounted) { | |
| setState(() { | |
| _services = services; | |
| _isLoading = false; | |
| }); | |
| } | |
| } catch (e) { | |
| if (mounted) { | |
| setState(() { | |
| _services = _getStaticServices(); | |
| _isLoading = false; | |
| }); | |
| } | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final scale = VoltResponsive.scale(context); | |
| final screenWidth = MediaQuery.of(context).size.width; | |
| final isMobile = screenWidth < 700; | |
| double sidePadding = isMobile ? 24.0 : 40.0; | |
| if (screenWidth > 1400) { | |
| sidePadding = (screenWidth - 1400) / 2 + 40.0; | |
| } | |
| int crossAxisCount = screenWidth > 1200 ? 3 : (screenWidth > 700 ? 2 : 1); | |
| var filteredServices = _services; | |
| if (filteredServices != null && _selectedCategory != 'ALL') { | |
| filteredServices = filteredServices | |
| .where((s) => s['category'] == _selectedCategory) | |
| .toList(); | |
| } | |
| return Scaffold( | |
| body: CustomScrollView( | |
| physics: const BouncingScrollPhysics(), | |
| slivers: [ | |
| _buildSliverAppBar(scale, isMobile ? 24.0 : 40.0), | |
| SliverToBoxAdapter( | |
| child: _buildCategoryFilter(scale, isMobile ? 24.0 : 40.0), | |
| ), | |
| if (_isLoading) | |
| const SliverFillRemaining( | |
| child: Center( | |
| child: CircularProgressIndicator( | |
| color: VoltColors.primaryFixedDim, | |
| ), | |
| ), | |
| ) | |
| else | |
| SliverPadding( | |
| padding: EdgeInsets.symmetric( | |
| horizontal: sidePadding, | |
| vertical: 40 * scale, | |
| ), | |
| sliver: SliverGrid( | |
| gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | |
| crossAxisCount: crossAxisCount, | |
| childAspectRatio: 1.35, | |
| crossAxisSpacing: 24, | |
| mainAxisSpacing: 24, | |
| ), | |
| delegate: SliverChildBuilderDelegate((context, index) { | |
| final service = filteredServices![index]; | |
| return AnimationConfiguration.staggeredGrid( | |
| position: index, | |
| duration: const Duration(milliseconds: 500), | |
| columnCount: crossAxisCount, | |
| child: FadeInAnimation( | |
| child: VoltServiceCard( | |
| title: service['title'] ?? 'PROGRAM', | |
| category: service['category'] ?? 'TRAINING', | |
| description: service['description'] ?? 'Exclusive training session tailored to your needs.', | |
| imageUrl: service['image_url'], | |
| scale: crossAxisCount >= 3 ? 0.75 : scale, | |
| ), | |
| ), | |
| ); | |
| }, childCount: filteredServices!.length), | |
| ), | |
| ), | |
| const SliverToBoxAdapter(child: SizedBox(height: 160)), // Nav spacer | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildSliverAppBar(double scale, double sidePadding) { | |
| return SliverAppBar( | |
| expandedHeight: 220.0 * scale, | |
| backgroundColor: Theme.of(context).scaffoldBackgroundColor, | |
| title: const VoltLogo(scale: 1.1), | |
| pinned: true, | |
| flexibleSpace: FlexibleSpaceBar( | |
| background: Stack( | |
| children: [ | |
| Positioned( | |
| right: -20, | |
| bottom: -40, | |
| child: Opacity( | |
| opacity: 0.25, | |
| child: Image.asset( | |
| 'assets/images/services.png', | |
| width: 300 * scale, | |
| height: 300 * scale, | |
| fit: BoxFit.contain, | |
| errorBuilder: (_, _, _) => const SizedBox(), | |
| ), | |
| ), | |
| ), | |
| Padding( | |
| padding: EdgeInsets.only(bottom: 40 * scale, left: sidePadding), | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.end, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| 'SERVICES', | |
| style: Theme.of(context).textTheme.displayMedium | |
| ?.copyWith(fontSize: 44 * scale, letterSpacing: -1), | |
| ), | |
| Text( | |
| 'CURATED PERFORMANCE PROGRAMS', | |
| style: Theme.of(context).textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| fontSize: 12 * scale, | |
| letterSpacing: 4, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildCategoryFilter(double scale, double sidePadding) { | |
| final categories = ['ALL', 'STRENGTH', 'CARDIO', 'YOGA', 'RECOVERY']; | |
| return Column( | |
| children: [ | |
| SizedBox( | |
| height: 70 * scale, | |
| child: ListView.builder( | |
| scrollDirection: Axis.horizontal, | |
| physics: const BouncingScrollPhysics(), | |
| padding: EdgeInsets.symmetric(horizontal: sidePadding), | |
| itemCount: categories.length, | |
| itemBuilder: (context, index) { | |
| final cat = categories[index]; | |
| final isSelected = _selectedCategory == cat; | |
| return Padding( | |
| padding: const EdgeInsets.only(right: 12.0), | |
| child: GestureDetector( | |
| onTap: () => setState(() => _selectedCategory = cat), | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 300), | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 20, | |
| vertical: 10, | |
| ), | |
| decoration: BoxDecoration( | |
| color: isSelected | |
| ? VoltColors.primaryFixedDim | |
| : Theme.of(context).colorScheme.surfaceContainerLow, | |
| borderRadius: BorderRadius.circular(40), | |
| ), | |
| child: Center( | |
| child: Text( | |
| cat, | |
| style: Theme.of(context).textTheme.labelSmall | |
| ?.copyWith( | |
| color: isSelected | |
| ? Colors.black | |
| : Theme.of(context).colorScheme.onSurfaceVariant, | |
| fontSize: 12 * scale, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| List<dynamic> _getStaticServices() { | |
| return [ | |
| {'title': 'HYPER TROPHY', 'category': 'STRENGTH'}, | |
| {'title': 'OXYGEN METRICS', 'category': 'CARDIO'}, | |
| {'title': 'NEURAL FLEX', 'category': 'YOGA'}, | |
| {'title': 'CRYOGENIC LAB', 'category': 'RECOVERY'}, | |
| ]; | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; | |
| import '../theme.dart'; | |
| import '../api_service.dart'; | |
| import '../widgets/volt_responsive.dart'; | |
| import '../widgets/volt_logo.dart'; | |
| import '../widgets/volt_card.dart'; | |
| class ShopScreen extends StatefulWidget { | |
| const ShopScreen({super.key}); | |
| @override | |
| State<ShopScreen> createState() => _ShopScreenState(); | |
| } | |
| class _ShopScreenState extends State<ShopScreen> { | |
| List<dynamic>? _products; | |
| bool _isLoading = true; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _loadData(); | |
| } | |
| Future<void> _loadData() async { | |
| try { | |
| final products = await ApiService.getProducts(); | |
| if (mounted) { | |
| setState(() { | |
| _products = products; | |
| _isLoading = false; | |
| }); | |
| } | |
| } catch (e) { | |
| if (mounted) { | |
| setState(() { | |
| _products = _getStaticProducts(); | |
| _isLoading = false; | |
| }); | |
| } | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final scale = VoltResponsive.scale(context); | |
| final screenWidth = MediaQuery.of(context).size.width; | |
| final isMobile = screenWidth < 700; | |
| int crossAxisCount = screenWidth > 1600 | |
| ? 4 | |
| : (screenWidth > 1100 ? 3 : (screenWidth > 700 ? 2 : 1)); | |
| double sidePadding = isMobile ? 24.0 : 40.0; | |
| if (screenWidth > 1600) { | |
| sidePadding = (screenWidth - 1600) / 2 + 40.0; | |
| } | |
| return Scaffold( | |
| body: CustomScrollView( | |
| physics: const BouncingScrollPhysics(), | |
| slivers: [ | |
| _buildSliverAppBar(scale, isMobile ? 24.0 : 40.0), | |
| if (_isLoading) | |
| const SliverFillRemaining( | |
| child: Center( | |
| child: CircularProgressIndicator( | |
| color: VoltColors.primaryFixedDim, | |
| ), | |
| ), | |
| ) | |
| else | |
| SliverPadding( | |
| padding: EdgeInsets.fromLTRB( | |
| sidePadding, | |
| 40 * scale, | |
| sidePadding, | |
| 160, | |
| ), | |
| sliver: SliverGrid( | |
| gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | |
| crossAxisCount: crossAxisCount, | |
| childAspectRatio: 0.82, | |
| crossAxisSpacing: 24, | |
| mainAxisSpacing: 32, | |
| ), | |
| delegate: SliverChildBuilderDelegate((context, index) { | |
| final product = _products![index]; | |
| return AnimationConfiguration.staggeredGrid( | |
| position: index, | |
| duration: const Duration(milliseconds: 600), | |
| columnCount: crossAxisCount, | |
| child: FadeInAnimation( | |
| child: VoltProductCard( | |
| title: product['title'], | |
| price: '\$${product['price']}', | |
| imageUrl: product['image_url'], | |
| isNew: (index % 3 == 0), | |
| scale: crossAxisCount >= 3 ? 0.7 : scale, | |
| ), | |
| ), | |
| ); | |
| }, childCount: _products!.length), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildSliverAppBar(double scale, double sidePadding) { | |
| return SliverAppBar( | |
| expandedHeight: 240.0 * scale, | |
| backgroundColor: Theme.of(context).scaffoldBackgroundColor, | |
| title: const VoltLogo(scale: 1.1), | |
| pinned: true, | |
| flexibleSpace: FlexibleSpaceBar( | |
| background: Stack( | |
| children: [ | |
| Positioned( | |
| right: -50, | |
| bottom: -60, | |
| child: Opacity( | |
| opacity: 0.2, | |
| child: Image.asset( | |
| 'assets/images/shop.png', | |
| width: 380 * scale, | |
| height: 380 * scale, | |
| fit: BoxFit.contain, | |
| errorBuilder: (_, _, _) => const SizedBox(), | |
| ), | |
| ), | |
| ), | |
| Padding( | |
| padding: EdgeInsets.only(bottom: 40 * scale, left: sidePadding), | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.end, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| 'COLLECTION', | |
| style: Theme.of(context).textTheme.displayMedium?.copyWith( | |
| fontSize: 44 * scale, | |
| letterSpacing: -1, | |
| ), | |
| ), | |
| Text( | |
| 'HIGH PERFORMANCE GEAR', | |
| style: Theme.of(context).textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| fontSize: 12 * scale, | |
| letterSpacing: 4, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| List<dynamic> _getStaticProducts() { | |
| return [ | |
| {'title': 'NITRO GRIP', 'price': '85.00'}, | |
| {'title': 'PULSE WRAP', 'price': '45.00'}, | |
| {'title': 'THERMO CORE', 'price': '120.00'}, | |
| {'title': 'KINETIC FOAM', 'price': '95.00'}, | |
| {'title': 'HYDRATE X', 'price': '35.00'}, | |
| {'title': 'RESTORE ROPES', 'price': '55.00'}, | |
| ]; | |
| } | |
| } | |
| import 'package:flutter/material.dart'; | |
| import '../theme.dart'; | |
| import '../api_service.dart'; | |
| import '../widgets/volt_responsive.dart'; | |
| import '../widgets/volt_logo.dart'; | |
| import '../widgets/volt_card.dart'; | |
| class ContactScreen extends StatefulWidget { | |
| const ContactScreen({super.key}); | |
| @override | |
| State<ContactScreen> createState() => _ContactScreenState(); | |
| } | |
| class _ContactScreenState extends State<ContactScreen> { | |
| final _formKey = GlobalKey<FormState>(); | |
| final _nameController = TextEditingController(); | |
| final _emailController = TextEditingController(); | |
| final _messageController = TextEditingController(); | |
| bool _isSubmitting = false; | |
| bool _isSuccess = false; | |
| void _submit() async { | |
| if (_formKey.currentState!.validate()) { | |
| setState(() => _isSubmitting = true); | |
| try { | |
| await ApiService.submitInquiry({ | |
| 'name': _nameController.text, | |
| 'email': _emailController.text, | |
| 'message': _messageController.text, | |
| }); | |
| if (!mounted) return; | |
| setState(() => _isSuccess = true); | |
| Future.delayed(const Duration(seconds: 4), () { | |
| if (mounted) { | |
| setState(() { | |
| _isSuccess = false; | |
| _isSubmitting = false; | |
| }); | |
| _formKey.currentState!.reset(); | |
| } | |
| }); | |
| } catch (e) { | |
| if (!mounted) return; | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar(content: Text('Submission failed. Please try again.')), | |
| ); | |
| setState(() => _isSubmitting = false); | |
| } | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final scale = VoltResponsive.scale(context); | |
| final isMobile = VoltResponsive.isMobile(context); | |
| final sidePadding = isMobile ? 24.0 : 60.0; | |
| return Scaffold( | |
| body: CustomScrollView( | |
| physics: const BouncingScrollPhysics(), | |
| slivers: [ | |
| _buildSliverAppBar(scale, isMobile, sidePadding), | |
| SliverToBoxAdapter( | |
| child: Center( | |
| child: ConstrainedBox( | |
| constraints: const BoxConstraints( | |
| maxWidth: 1000, | |
| ), // Constraint for better readability | |
| child: Padding( | |
| padding: EdgeInsets.symmetric( | |
| horizontal: sidePadding, | |
| vertical: 40 * scale, | |
| ), | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| _buildContactMethods(scale, isMobile), | |
| const SizedBox(height: 60), | |
| _isSuccess | |
| ? _buildSuccessState(scale) | |
| : _buildFormLayout(scale, isMobile), | |
| const SizedBox(height: 160), // Nav spacer | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildSliverAppBar(double scale, bool isMobile, double sidePadding) { | |
| return SliverAppBar( | |
| expandedHeight: 240.0 * scale, | |
| backgroundColor: Theme.of(context).scaffoldBackgroundColor, | |
| title: const VoltLogo(scale: 1.1), | |
| pinned: true, | |
| flexibleSpace: FlexibleSpaceBar( | |
| background: Stack( | |
| children: [ | |
| Positioned( | |
| right: -30, | |
| bottom: -20, | |
| child: Opacity( | |
| opacity: 0.2, | |
| child: Image.asset( | |
| 'assets/images/contact.png', | |
| width: 320 * scale, | |
| height: 320 * scale, | |
| fit: BoxFit.contain, | |
| errorBuilder: (_, _, _) => const SizedBox(), | |
| ), | |
| ), | |
| ), | |
| Padding( | |
| padding: EdgeInsets.only(bottom: 40 * scale, left: sidePadding), | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.end, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| 'CONTACT', | |
| style: Theme.of(context).textTheme.displayMedium?.copyWith( | |
| fontSize: (isMobile ? 44.0 : 64.0) * scale, | |
| letterSpacing: -1, | |
| ), | |
| ), | |
| Text( | |
| 'SYNC WITH OUR PERFORMANCE TEAM', | |
| style: Theme.of(context).textTheme.labelMedium?.copyWith( | |
| color: VoltColors.primaryFixedDim, | |
| fontSize: (isMobile ? 12.0 : 14.0) * scale, | |
| letterSpacing: 4, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildContactMethods(double scale, bool isMobile) { | |
| return Column( | |
| children: [ | |
| VoltActionCard( | |
| title: 'ELITE SUPPORT', | |
| subtitle: 'support@voltkinectic.fit', | |
| icon: Icons.support_agent, | |
| scale: isMobile ? 1.0 : 0.85, | |
| ), | |
| const SizedBox(height: 16), | |
| VoltActionCard( | |
| title: 'PERFORMANCE HOTLINE', | |
| subtitle: '+1 (555) VOLT-FIT', | |
| icon: Icons.phone_android, | |
| scale: isMobile ? 1.0 : 0.85, | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildFormLayout(double scale, bool isMobile) { | |
| return Center( | |
| child: Container( | |
| padding: EdgeInsets.all(32 * scale), | |
| decoration: BoxDecoration( | |
| color: VoltColors.surfaceContainerLow, | |
| borderRadius: BorderRadius.circular(32), | |
| border: Border.all(color: VoltColors.surfaceContainerHighest), | |
| ), | |
| child: Form( | |
| key: _formKey, | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.stretch, | |
| children: [ | |
| _buildField('NAME', _nameController, scale), | |
| const SizedBox(height: 32), | |
| _buildField('EMAIL', _emailController, scale), | |
| const SizedBox(height: 32), | |
| _buildField('MESSAGE', _messageController, scale, maxLines: 5), | |
| const SizedBox(height: 48), | |
| _buildSubmitButton(scale), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildField( | |
| String label, | |
| TextEditingController controller, | |
| double scale, { | |
| int maxLines = 1, | |
| }) { | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| label, | |
| style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith( | |
| color: VoltColors.onSurfaceVariant.withValues(alpha: 0.6), | |
| fontSize: 12 * scale, | |
| letterSpacing: 2, | |
| ), | |
| ), | |
| const SizedBox(height: 12), | |
| TextFormField( | |
| controller: controller, | |
| maxLines: maxLines, | |
| style: VoltTheme.darkTheme.textTheme.bodyLarge?.copyWith( | |
| fontSize: 17 * scale, | |
| color: Colors.white, | |
| ), | |
| decoration: InputDecoration( | |
| filled: true, | |
| fillColor: VoltColors.surfaceContainerHigh.withValues(alpha: 0.5), | |
| border: OutlineInputBorder( | |
| borderRadius: BorderRadius.circular(20 * scale), | |
| borderSide: BorderSide.none, | |
| ), | |
| contentPadding: const EdgeInsets.all(24), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildSuccessState(double scale) { | |
| return Center( | |
| child: Container( | |
| padding: EdgeInsets.all(48 * scale), | |
| decoration: BoxDecoration( | |
| color: VoltColors.surfaceContainerLow, | |
| borderRadius: BorderRadius.circular(32), | |
| ), | |
| child: Column( | |
| children: [ | |
| Container( | |
| padding: const EdgeInsets.all(24), | |
| decoration: const BoxDecoration( | |
| color: VoltColors.primaryFixedDim, | |
| shape: BoxShape.circle, | |
| ), | |
| child: const Icon(Icons.check, color: Colors.black, size: 48), | |
| ), | |
| const SizedBox(height: 32), | |
| Text( | |
| 'PULSE RECEIVED', | |
| style: VoltTheme.darkTheme.textTheme.headlineLarge?.copyWith( | |
| fontSize: 26 * scale, | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| Text( | |
| "Our performance team is analyzing your request.", | |
| textAlign: TextAlign.center, | |
| style: VoltTheme.darkTheme.textTheme.bodyMedium?.copyWith( | |
| fontSize: 15 * scale, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildSubmitButton(double scale) { | |
| return InkWell( | |
| onTap: _isSubmitting ? null : _submit, | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 300), | |
| height: 70 * scale, | |
| decoration: BoxDecoration( | |
| gradient: _isSubmitting ? null : VoltColors.primaryGradient, | |
| color: _isSubmitting ? VoltColors.surfaceContainerHigh : null, | |
| borderRadius: BorderRadius.circular(40 * scale), | |
| ), | |
| child: Center( | |
| child: _isSubmitting | |
| ? const CircularProgressIndicator(color: Colors.black) | |
| : Text( | |
| 'SEND PULSE', | |
| style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith( | |
| color: Colors.black, | |
| fontSize: 16 * scale, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment