Skip to content

Instantly share code, notes, and snippets.

@Slozzyondul
Last active April 15, 2026 17:16
Show Gist options
  • Select an option

  • Save Slozzyondul/58c9c812af9d3464a68dd66e1f2d41cd to your computer and use it in GitHub Desktop.

Select an option

Save Slozzyondul/58c9c812af9d3464a68dd66e1f2d41cd to your computer and use it in GitHub Desktop.
gym2.dart
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