Skip to content

Instantly share code, notes, and snippets.

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

  • Save Slozzyondul/9279ab37ed94e351b87ea4407be80462 to your computer and use it in GitHub Desktop.

Select an option

Save Slozzyondul/9279ab37ed94e351b87ea4407be80462 to your computer and use it in GitHub Desktop.
starting a gym app here i need to utilize dart full-stack mode any tips, i have coupled the main parts of the app into a single file the home code, admin code, router code and services
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'screens/home_screen.dart';
import 'screens/services_screen.dart';
import 'screens/shop_screen.dart';
import 'screens/contact_screen.dart';
import 'screens/admin_screen.dart';
import 'widgets/volt_nav_bar.dart';
void main() {
runApp(const ProviderScope(child: VoltApp()));
}
class VoltApp extends ConsumerWidget {
const VoltApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
final router = ref.watch(routerProvider);
return MaterialApp.router(
title: 'Volt Kinetic',
debugShowCheckedModeBanner: false,
theme: VoltTheme.lightTheme,
darkTheme: VoltTheme.darkTheme,
themeMode: themeMode,
routerConfig: router,
);
}
}
final routerProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: '/',
routes: [
ShellRoute(
builder: (context, state, child) {
return MainScaffold(state: state, child: child);
},
routes: [
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/services',
builder: (context, state) => const ServicesScreen(),
),
GoRoute(
path: '/shop',
builder: (context, state) => const ShopScreen(),
),
GoRoute(
path: '/contact',
builder: (context, state) => const ContactScreen(),
),
GoRoute(
path: '/admin',
builder: (context, state) => const AdminScreen(),
),
],
),
],
);
});
class MainScaffold extends StatelessWidget {
final Widget child;
final GoRouterState state;
const MainScaffold({super.key, required this.child, required this.state});
@override
Widget build(BuildContext context) {
// Determine active index safely from state.matchedLocation
final String location = state.matchedLocation;
int activeIndex = 0;
if (location.startsWith('/services')) {
activeIndex = 1;
} else if (location.startsWith('/shop')) {
activeIndex = 2;
} else if (location.startsWith('/contact')) {
activeIndex = 3;
} else if (location.startsWith('/admin')) {
activeIndex = 4;
}
return Scaffold(
backgroundColor: Colors.black, // Ensure we have a base color
body: child,
// Using a Stack to place the Nav Bar to ensure it doesn't displace the body
// and allows for proper 'floating' aesthetic requested earlier.
bottomNavigationBar: SizedBox(
height: 110, // Explicit height for bottomNavigationBar area
child: VoltNavBar(
activeIndex: activeIndex,
onIndexChanged: (index) {
switch (index) {
case 0:
context.go('/');
break;
case 1:
context.go('/services');
break;
case 2:
context.go('/shop');
break;
case 3:
context.go('/contact');
break;
case 4:
context.go('/admin');
break;
}
},
),
),
);
}
}
class ApiService {
static const String baseUrl = 'http://127.0.0.1:5000/api';
static Future<List<dynamic>> getServices() async {
final response = await http.get(Uri.parse('$baseUrl/services'));
if (response.statusCode == 200) {
return json.decode(response.body);
}
throw Exception('Failed to load services');
}
static Future<List<dynamic>> getProducts() async {
final response = await http.get(Uri.parse('$baseUrl/products'));
if (response.statusCode == 200) {
return json.decode(response.body);
}
throw Exception('Failed to load products');
}
static Future<void> submitInquiry(Map<String, String> data) async {
final response = await http.post(
Uri.parse('$baseUrl/inquiries'),
headers: {'Content-Type': 'application/json'},
body: json.encode(data),
);
if (response.statusCode != 201) {
throw Exception('Failed to submit inquiry');
}
}
// Admin methods
static Future<void> addService(Map<String, dynamic> data) async {
await http.post(
Uri.parse('$baseUrl/admin/services'),
headers: {'Content-Type': 'application/json'},
body: json.encode(data),
);
}
static Future<void> updateService(int id, Map<String, dynamic> data) async {
await http.put(
Uri.parse('$baseUrl/admin/services/$id'),
headers: {'Content-Type': 'application/json'},
body: json.encode(data),
);
}
static Future<void> deleteService(int id) async {
await http.delete(Uri.parse('$baseUrl/admin/services/$id'));
}
static Future<void> addProduct(Map<String, dynamic> data) async {
await http.post(
Uri.parse('$baseUrl/admin/products'),
headers: {'Content-Type': 'application/json'},
body: json.encode(data),
);
}
static Future<void> updateProduct(int id, Map<String, dynamic> data) async {
await http.put(
Uri.parse('$baseUrl/admin/products/$id'),
headers: {'Content-Type': 'application/json'},
body: json.encode(data),
);
}
static Future<void> deleteProduct(int id) async {
await http.delete(Uri.parse('$baseUrl/admin/products/$id'));
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
late AnimationController _pulseController;
List<dynamic>? _services;
bool _isLoading = true;
@override
void initState() {
super.initState();
_pulseController = AnimationController(
vsync: this,
duration: const Duration(seconds: 4),
)..repeat(reverse: true);
_loadData();
}
Future<void> _loadData() async {
try {
final services = await ApiService.getServices();
if (mounted) {
setState(() {
_services = services;
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_services = _getStaticData();
_isLoading = false;
});
}
}
}
@override
void dispose() {
_pulseController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final scale = VoltResponsive.scale(context);
final isMobile = VoltResponsive.isMobile(context);
final screenWidth = MediaQuery.of(context).size.width;
double sidePadding = isMobile ? 24.0 : 60.0;
if (screenWidth > 1400) {
sidePadding = (screenWidth - 1400) / 2 + 60.0;
}
return Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(scale, isMobile ? 24.0 : 60.0),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: sidePadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: AnimationConfiguration.toStaggeredList(
duration: const Duration(milliseconds: 800),
childAnimationBuilder: (widget) => SlideAnimation(
verticalOffset: 50.0,
curve: Curves.easeOutQuart,
child: FadeInAnimation(child: widget),
),
children: [
const SizedBox(height: 60),
_buildHeroText(context, scale, isMobile),
const SizedBox(height: 80),
_isLoading
? const Center(
child: CircularProgressIndicator(
color: VoltColors.primaryFixedDim,
),
)
: _buildFeaturedServices(context, scale, isMobile),
const SizedBox(height: 160),
],
),
),
),
),
],
),
);
}
Widget _buildSliverAppBar(double scale, double sidePadding) {
return SliverAppBar(
expandedHeight: 120.0 * scale,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
toolbarHeight: 80,
title: const VoltLogo(scale: 1.1),
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment.topLeft,
radius: 1.5,
colors: [
VoltColors.primaryFixedDim.withValues(alpha: 0.05),
Colors.transparent,
],
),
),
),
),
);
}
Widget _buildHeroText(BuildContext context, double scale, bool isMobile) {
final theme = Theme.of(context);
final displayStyle = theme.textTheme.displayLarge;
final fontSize = (isMobile ? 48.0 : 72.0) * scale;
final onSurface = theme.colorScheme.onSurface;
return Stack(
clipBehavior: Clip.none,
children: [
Positioned(
right: -40,
top: -100,
child: Opacity(
opacity: 0.25,
child: Image.asset(
'assets/images/home.png',
width: 350 * scale,
height: 500 * scale,
fit: BoxFit.contain,
errorBuilder: (_, _, _) => const SizedBox(),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'THE',
style: displayStyle?.copyWith(
color: onSurface,
fontSize: fontSize,
letterSpacing: -2,
),
),
ShaderMask(
shaderCallback: (b) => VoltColors.primaryGradient.createShader(b),
child: Text(
'ELECTRIC',
style: displayStyle?.copyWith(
fontSize: fontSize,
color: Colors.white,
letterSpacing: -2,
),
),
),
Text(
'PULSE.',
style: displayStyle?.copyWith(
color: onSurface,
fontSize: fontSize,
letterSpacing: -2,
),
),
const SizedBox(height: 24),
SizedBox(
width: (isMobile ? 300.0 : 450.0) * scale,
child: Text(
'HIGH INTENSITY MEETS HIGH SOPHISTICATION. EXPERIENCE THE NEW STANDARD OF MOTION.',
style: theme.textTheme.bodyLarge?.copyWith(
height: 1.6,
color: onSurface.withValues(alpha: 0.7),
fontSize: (isMobile ? 15.0 : 18.0) * scale,
),
),
),
],
),
],
);
}
Widget _buildFeaturedServices(
BuildContext context,
double scale,
bool isMobile,
) {
final services = _services!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const VoltSectionHeader(title: 'SERVICES', actionText: 'EXPLORE ALL'),
const SizedBox(height: 32),
SizedBox(
height: 300 * scale,
child: ListView.builder(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: services.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(right: 24.0),
child: VoltServiceCard(
title: services[index]['title'],
category: services[index]['category'],
description: services[index]['description'],
imageUrl: services[index]['image_url'],
scale: isMobile ? 1.0 : 0.85,
),
);
},
),
),
],
);
}
List<dynamic> _getStaticData() {
return [
{'title': 'HYPER TROPHY', 'category': 'STRENGTH'},
{'title': 'OXYGEN METRICS', 'category': 'CARDIO'},
{'title': 'NEURAL FLEX', 'category': 'YOGA'},
];
}
}
class AdminScreen extends StatefulWidget {
const AdminScreen({super.key});
@override
State<AdminScreen> createState() => _AdminScreenState();
}
class _AdminScreenState extends State<AdminScreen> {
final _titleController = TextEditingController();
final _descController = TextEditingController();
final _priceController = TextEditingController();
bool _isService = true;
bool _isSubmitting = false;
void _addItem() async {
if (_titleController.text.isEmpty) return;
setState(() => _isSubmitting = true);
final Map<String, dynamic> data = {
'title': _titleController.text,
'description': _descController.text,
};
try {
if (_isService) {
data['category'] = 'STRENGTH';
await ApiService.addService(data);
} else {
data['price'] = double.tryParse(_priceController.text) ?? 0.0;
await ApiService.addProduct(data);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Operational Objective Complete')),
);
_titleController.clear();
_descController.clear();
_priceController.clear();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Sync Error.')),
);
} finally {
if (mounted) setState(() => _isSubmitting = false);
}
}
@override
Widget build(BuildContext context) {
final scale = VoltResponsive.scale(context);
final sidePadding = VoltResponsive.isMobile(context) ? 24.0 : 40.0;
return Scaffold(
backgroundColor: VoltColors.surface,
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(scale, sidePadding),
SliverToBoxAdapter(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1000),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: sidePadding, vertical: 40 * scale),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStats(scale),
const SizedBox(height: 48),
_buildControlPanel(scale),
const SizedBox(height: 160), // Nav spacer
],
),
),
),
),
),
],
),
);
}
Widget _buildSliverAppBar(double scale, double sidePadding) {
return SliverAppBar(
expandedHeight: 180.0 * scale,
backgroundColor: VoltColors.surface,
title: const VoltLogo(scale: 1.1),
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Padding(
padding: EdgeInsets.only(bottom: 40 * scale, left: sidePadding),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'SYSTEM ADMIN',
style: VoltTheme.darkTheme.textTheme.displayMedium?.copyWith(
fontSize: 38 * scale,
letterSpacing: -1,
),
),
Text(
'INFRASTRUCTURE MANAGEMENT',
style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith(
color: VoltColors.primaryFixedDim,
fontSize: 12 * scale,
letterSpacing: 4,
),
),
],
),
),
),
);
}
Widget _buildStats(double scale) {
return Column(
children: [
VoltActionCard(
title: 'SYSTEM STATUS',
subtitle: 'OPERATIONAL - SYNCED',
icon: Icons.check_circle_outline,
scale: scale * 0.9,
),
const SizedBox(height: 16),
VoltActionCard(
title: 'DATABASE NODES',
subtitle: 'FLASK API ACTIVE @ PORT 5000',
icon: Icons.storage_outlined,
scale: scale * 0.9,
),
],
);
}
Widget _buildControlPanel(double scale) {
return Container(
padding: EdgeInsets.all(32 * scale),
decoration: BoxDecoration(
color: VoltColors.surfaceContainerLow,
borderRadius: BorderRadius.circular(30),
border: Border.all(color: VoltColors.surfaceContainerHighest),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTypeToggle(scale),
const SizedBox(height: 40),
_buildField('OBJECTIVE NAME (TITLE)', _titleController, scale),
const SizedBox(height: 24),
_buildField('INTEL (DESCRIPTION)', _descController, scale, maxLines: 4),
if (!_isService) ...[
const SizedBox(height: 24),
_buildField('VALUATION (PRICE)', _priceController, scale, isNum: true),
],
const SizedBox(height: 48),
_buildActionButton(scale),
],
),
);
}
Widget _buildTypeToggle(double scale) {
return Container(
height: 54 * scale,
decoration: BoxDecoration(color: VoltColors.surfaceContainerLow, borderRadius: BorderRadius.circular(27 * scale)),
child: Row(
children: [
_buildToggle('SERVICE', _isService, () => setState(() => _isService = true), scale),
_buildToggle('PRODUCT', !_isService, () => setState(() => _isService = false), scale),
],
),
);
}
Widget _buildToggle(String label, bool active, VoidCallback onTap, double scale) {
return Expanded(
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
alignment: Alignment.center,
decoration: BoxDecoration(color: active ? VoltColors.primaryFixedDim : Colors.transparent, borderRadius: BorderRadius.circular(27 * scale)),
child: Text(label, style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith(color: active ? Colors.black : VoltColors.onSurfaceVariant, fontWeight: FontWeight.bold, fontSize: 13 * scale)),
),
),
);
}
Widget _buildField(String label, TextEditingController controller, double scale, {int maxLines = 1, bool isNum = false}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith(color: VoltColors.onSurfaceVariant, fontSize: 11 * scale, letterSpacing: 2)),
const SizedBox(height: 12),
TextField(
controller: controller,
maxLines: maxLines,
keyboardType: isNum ? const TextInputType.numberWithOptions(decimal: true) : TextInputType.text,
style: VoltTheme.darkTheme.textTheme.bodyLarge?.copyWith(fontSize: 17 * scale),
decoration: InputDecoration(
filled: true,
fillColor: VoltColors.surfaceContainerHigh.withValues(alpha: 0.5),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16 * scale), borderSide: BorderSide.none),
),
),
],
);
}
Widget _buildActionButton(double scale) {
return InkWell(
onTap: _isSubmitting ? null : _addItem,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 64 * scale,
decoration: BoxDecoration(gradient: _isSubmitting ? null : VoltColors.primaryGradient, color: _isSubmitting ? VoltColors.surfaceContainerLow : null, borderRadius: BorderRadius.circular(32 * scale)),
child: Center(child: _isSubmitting ? const CircularProgressIndicator(color: Colors.black) : Text('SYNC SYSTEM DATA', style: VoltTheme.darkTheme.textTheme.labelMedium?.copyWith(color: Colors.black, fontSize: 15 * scale, fontWeight: FontWeight.bold))),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment