// This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'dart:async'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: RunningScreen(), ); } } class RunningScreen extends StatefulWidget { const RunningScreen({super.key}); @override State createState() => _RunningScreenState(); } class _RunningScreenState extends State with SingleTickerProviderStateMixin { double progress = 0.0; double targetDistance = 8.0; double currentDistance = 0.0; Duration elapsedTime = Duration.zero; double averageSpeed = 0.0; double bestPace = 0.0; Timer? _timer; bool isTracking = false; late AnimationController _animationController; late Animation _walkingAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _walkingAnimation = Tween(begin: -20, end: 20).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); _animationController.repeat(reverse: true); } @override void dispose() { _timer?.cancel(); _animationController.dispose(); super.dispose(); } void _startTracking() { if (isTracking) return; setState(() { isTracking = true; }); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { elapsedTime = Duration(seconds: elapsedTime.inSeconds + 1); // Simulate distance progression (adjust rate as needed) currentDistance += 0.05; // 0.05km per second = 3km/h // Calculate progress progress = (currentDistance / targetDistance).clamp(0.0, 1.0); // Calculate average speed (km/h) if (elapsedTime.inSeconds > 0) { averageSpeed = (currentDistance / elapsedTime.inSeconds) * 3600; } // Update best pace if (averageSpeed > bestPace) { bestPace = averageSpeed; } // Stop when target is reached if (progress >= 1.0) { _stopTracking(); } }); }); } void _stopTracking() { _timer?.cancel(); setState(() { isTracking = false; }); } void _resetTracking() { _stopTracking(); setState(() { progress = 0.0; currentDistance = 0.0; elapsedTime = Duration.zero; averageSpeed = 0.0; bestPace = 0.0; }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[300], body: Center( child: Container( width: 350, height: 700, decoration: BoxDecoration( color: Colors.grey[700], borderRadius: BorderRadius.circular(40), ), child: Stack( children: [ /// PROGRESSO Positioned( top: 80, left: 75, child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: progress), duration: const Duration(milliseconds: 500), builder: (context, value, _) { return SizedBox( width: 200, height: 200, child: Stack( alignment: Alignment.center, children: [ CircularProgressIndicator( value: value, strokeWidth: 15, backgroundColor: Colors.black26, valueColor: const AlwaysStoppedAnimation( Colors.white), ), Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "${(value * 100).toInt()}%", style: const TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white, ), ), const Text( "COMPLETO", style: TextStyle(color: Colors.white70), ), const SizedBox(height: 8), Text( "${targetDistance.toStringAsFixed(1)} KM", style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ], ) ], ), ); }, ), ), /// DISTANCE DISPLAY Positioned( top: 290, left: 0, right: 0, child: Center( child: Text( "${currentDistance.toStringAsFixed(1)} KM | ${targetDistance.toStringAsFixed(1)} KM", style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), /// ESTATÍSTICAS Positioned( top: 330, left: 30, right: 30, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(20), ), child: Column( children: [ InfoRow("MELHOR RITMO", "${bestPace.toStringAsFixed(1)} KM/H"), InfoRow("TRAJETO", "${currentDistance.toStringAsFixed(2)} KM"), InfoRow("TEMPO", _formatDuration(elapsedTime)), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "VEL. MÉDIA ${averageSpeed.toStringAsFixed(1)} KM/H", style: const TextStyle(color: Colors.white, fontSize: 16), ), IconButton( icon: const Icon(Icons.refresh, color: Colors.white), onPressed: _resetTracking, iconSize: 20, ), ], ), ], ), ), ), /// BARRA DE PROGRESSO COM AVATAR Positioned( bottom: 130, left: 30, right: 30, child: Column( children: [ SizedBox( height: 40, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(20), child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: progress), duration: const Duration(milliseconds: 500), builder: (context, value, _) { return LinearProgressIndicator( value: value, minHeight: 12, backgroundColor: Colors.black26, valueColor: const AlwaysStoppedAnimation( Colors.white), ); }, ), ), // Walking avatar Positioned( left: progress * (280 - 30), // Adjust position based on progress top: 0, child: AnimatedBuilder( animation: _walkingAnimation, builder: (context, child) { return Transform.translate( offset: Offset(0, _walkingAnimation.value), child: child, ); }, child: const Icon( Icons.directions_walk, color: Colors.white, size: 30, ), ), ), ], ), ), const SizedBox(height: 10), // Start/Stop button ElevatedButton( onPressed: isTracking ? _stopTracking : _startTracking, style: ElevatedButton.styleFrom( backgroundColor: isTracking ? Colors.red : Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), ), child: Text( isTracking ? "PARAR" : "COMEÇAR", style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ], ), ), /// MENU INFERIOR Positioned( bottom: 30, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ const Icon(Icons.group, color: Colors.white), const Icon(Icons.access_time, color: Colors.white), Stack( children: [ const Icon(Icons.notifications, color: Colors.white), Positioned( right: 0, top: 0, child: Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), ], ), GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Avatar clicado!'), duration: Duration(seconds: 1), ), ); }, child: const CircleAvatar( backgroundColor: Colors.blue, child: Icon(Icons.person, color: Colors.white), ), ), ], ), ), /// Bluetooth icon Positioned( top: 20, right: 20, child: Stack( children: [ const Icon(Icons.bluetooth, color: Colors.white, size: 24), Positioned( right: 0, top: 0, child: Container( width: 6, height: 6, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), ], ), ), ], ), ), ), ); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); String hours = twoDigits(duration.inHours); String minutes = twoDigits(duration.inMinutes.remainder(60)); return "${hours}H${minutes}M"; } } class InfoRow extends StatelessWidget { final String title; final String value; const InfoRow(this.title, this.value, {super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(title, style: const TextStyle(color: Colors.white70)), Text(value, style: const TextStyle(color: Colors.white)), ], ), ); } }