atualização

This commit is contained in:
Carlos Correia
2026-03-05 18:08:06 +00:00
parent ebca3cfdce
commit 6931d6ada2
18 changed files with 725 additions and 2191 deletions

View File

@@ -1,121 +0,0 @@
# Configuração do Google Maps API
## ✅ **JÁ CONFIGURADO - ROTAS REAIS DO GOOGLE MAPS**
Este projeto agora tem um mapa **100% funcional** com rotas **realistas** do Google Maps!
### 🗺️ **Funcionalidades Implementadas:**
-**Localização em tempo real** do usuário
-**Pesquisa de destinos** (simulada para demonstração)
-**Rotas REALISTAS** usando Google Directions API
-**Caminho pelas ruas** (nÃO é linha reta!)
-**Permissões** configuradas para Android e iOS
-**Interface intuitiva** com barra de busca
-**Feedback visual** com SnackBars funcionais
### <20> **Como as Rotas Funcionam:**
**🔍 Google Directions API:**
- Faz requisição real para `maps.googleapis.com/maps/api/directions`
- Parâmetros: `origin`, `destination`, `mode=walking`
- Retorna passos detalhados da rota
- Extrai pontos de cada passo (`start_location`, `end_location`)
- Cria polyline com todos os pontos conectados
**📍 Resultado Visual:**
-**Linha azul** seguindo ruas reais
-**Curvas e quinas** como GPS real
-**Pontos intermediários** das direções
-**Ajuste automático** de câmera
### <20>📱 **Como Usar:**
1. **Abrir o app** → Solicita permissão de localização
2. **Aguardar GPS** → Mostra sua localização atual (marcador azul)
3. **Pesquisar destino** → Digite "Parque Ibirapuera" ou clique nos chips
4. **Ver rota REAL** → Linha azul segue ruas reais
5. **Controles** → Zoom e botão de localização
### 🔧 **Configuração Técnica:**
#### Android:
-`AndroidManifest.xml` - Permissões de localização
- ✅ API Key configurada
-`google_maps_flutter` v2.5.3
-`geolocator` v10.1.0
-`http` v1.1.0 (para Directions API)
#### iOS:
-`Info.plist` - Permissões de localização
-`AppDelegate.swift` - API Key configurada
-`Podfile` - Dependências iOS
### 🎯 **Funcionalidades do Mapa:**
**📍 Localização:**
- Obtém GPS automaticamente
- Marcador azul para posição atual
- Botão "Minha Localização"
**🔍 Pesquisa:**
- Barra de busca intuitiva
- Sugestões de lugares populares
- Loading indicator durante busca
**🛣️ Rota REALISTA:**
- **Google Directions API** integrada
- **Caminho pelas ruas** (linha curva)
- **Pontos detalhados** de cada passo
- **Distância e tempo** reais da API
**📊 Estatísticas:**
- Distância real da API
- Tempo real da API
- Modo: Caminhada
### 🚀 **Para Testar:**
```bash
# Limpar e instalar dependências
flutter clean
flutter pub get
# Rodar o app
flutter run
```
### ⚠️ **Importante:**
- **Permissões necessárias** - Aceite quando solicitado
- **GPS ativo** - Necessário para localização precisa
- **Internet** - Para Directions API
- **Modo avião** - Desative para funcionar
### 📋 **Arquivos Configurados:**
- `lib/screens/google_maps_screen.dart` - Mapa com Directions API ✅
- `android/app/src/main/AndroidManifest.xml` - Permissões Android ✅
- `ios/Runner/Info.plist` - Permissões iOS ✅
- `ios/Runner/AppDelegate.swift` - API Key iOS ✅
- `pubspec.yaml` - Dependências incluindo http ✅
### 🔄 **Como as Rotas São Calculadas:**
1. **Requisição HTTP** para Google Directions API
2. **Parâmetros:** origin, destination, mode=walking
3. **Resposta JSON** com routes[0].legs[0].steps[]
4. **Extração** de start_location e end_location
5. **Criação** de polyline com pontos conectados
6. **Renderização** no mapa como linha azul
**Rotas 100% realistas e funcionais!** 🎉✨
### <20> **Exemplo de Console:**
```
🛣️ Buscando rota real com Google Directions API...
🌐 URL da requisição: https://maps.googleapis.com/maps/api/directions/json?origin=-23.5505,-46.6333&destination=-23.5874,-46.6576&mode=walking&language=pt_BR&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k
📡 Status code: 200
✅ Rota encontrada com 12 passos
📍 Total de pontos na rota: 14
📊 Distância: 2.3 km, Tempo: 28 min
```
**Mapa com rotas realistas pronto para uso!** 🗺️🛣️

View File

@@ -3,8 +3,12 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application <application
android:label="run_vision_pro" android:label="teste_projeto_turma"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
@@ -35,7 +39,7 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<!-- Google Maps API Key --> <!-- TODO: Replace with your actual Google Maps API Key -->
<meta-data android:name="com.google.android.geo.API_KEY" <meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k"/> android:value="AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k"/>
</application> </application>

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
class AppColors { class AppColors {
// Background colors // Background colors
static const Color background = Color.fromARGB(255, 49, 53, 77); static const Color background = Color.fromARGB(255, 48, 48, 51);
static const Color coral = Color(0xFFFF6B6B); static const Color coral = Color(0xFFFF6B6B);
// Button colors // Button colors
@@ -11,4 +11,10 @@ class AppColors {
// Neutral colors // Neutral colors
static const Color backgroundGrey = Color.fromARGB(255, 89, 89, 89); static const Color backgroundGrey = Color.fromARGB(255, 89, 89, 89);
static const Color black = Colors.black;
// Status colors
static const Color error = Colors.red;
static const Color success = Colors.green;
static const Color transparent = Colors.transparent;
} }

View File

@@ -1,8 +1,61 @@
class AppStrings { class AppStrings {
// Initial screen // Main Screen
static const String registrar = 'Registrar'; static const String complete = "COMPLETO";
static const String entrar = 'Entrar'; static const String steps = "PASSOS";
static const String bpm = "BPM";
static const String kcal = "K/CAL";
static const String mapPreview = "MAPA";
static const String settings = "Configurações";
static const String groups = "Grupos";
static const String history = "Histórico";
static const String notifications = "Notificações";
static const String profile = "Perfil";
// Common // Bluetooth Screen
static const String appTitle = 'Run Vision Pro'; static const String bluetoothTitle = "DISPOSITIVOS";
static const String bluetoothConnect = "CONECTAR BLUETOOTH";
static const String stopSearch = "PARAR BUSCA";
static const String startSearch = "BUSCAR AGORA";
static const String searching = "STATUS: BUSCANDO...";
static const String statusReady = "STATUS: PRONTO";
static const String statusConnected = "STATUS: CONECTADO";
static const String nearbyDevices = "Dispositivos próximos";
static const String active = "ATIVO";
static const String startSearchInstruction = "Inicie a busca para conectar";
static const String connect = "CONECTAR";
static const String noDevicesFound = "Nenhum dispositivo encontrado.";
static const String foundDevices = "Encontrados";
static const String oneDevice = "1 Dispositivo";
static const String permissionsDenied = "Permissões de Bluetooth negadas.";
static const String turnOnBluetooth =
"Ligue o Bluetooth para buscar dispositivos.";
static const String scanError = "Erro ao iniciar scan: ";
static const String stopScanError = "Erro ao parar scan: ";
static const String defaultDeviceName = "Dispositivo";
static const String connectingTo = "Conectando a ";
static const String connectedSuccess = "Conectado com sucesso!";
static const String connectFail = "Falha ao conectar: ";
static const String deviceIdPrefix = "Disp. [";
static const String unknownDevice = "Dispositivo Desconhecido";
// Map Screen
static const String mapTitleTracking = "TRACKING ATIVO";
static const String mapTitlePlanning = "PLANEJAR TRAJETO";
static const String mapTitleRunning = "CORRIDA";
static const String mapPace = "RITMO";
static const String mapRoute = "TRAJETO";
static const String kmhUnit = "KM/H";
static const String kmUnit = "KM";
static const String planningInstruction = "Toque para definir Início e Fim";
static const String btnStop = "PARAR";
static const String btnSimulate = "SIMULAR";
static const String btnStartRun = "INICIAR CORRIDA";
static const String btnStopRun = "PARAR CORRIDA";
static const String startPoint = "Partida";
static const String finishPoint = "Chegada";
// Auth Screens
static const String entrar = "ENTRAR";
static const String registrar = "REGISTRAR";
static const String appTitle = "Run Vision Pro";
} }

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'screens/inicial_screen.dart'; import 'screens/inicial_screen.dart';
import 'constants/app_strings.dart'; import 'constants/app_strings.dart';
import 'services/supabase_service.dart'; import 'services/supabase_service.dart';
import 'constants/app_constants.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@@ -38,7 +37,9 @@ class MyApp extends StatelessWidget {
// //
// This works for code too, not just values: Most code changes can be // This works for code too, not just values: Most code changes can be
// tested with just a hot reload. // tested with just a hot reload.
colorScheme: .fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(
seedColor: Colors.white.withValues(alpha: 0.1),
),
), ),
home: const InicialScreen(), home: const InicialScreen(),
); );

View File

View File

@@ -1,891 +0,0 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../constants/app_colors.dart';
class GoogleMapScreen extends StatefulWidget {
const GoogleMapScreen({super.key});
@override
State<GoogleMapScreen> createState() => _GoogleMapScreenState();
}
class _GoogleMapScreenState extends State<GoogleMapScreen> {
late GoogleMapController mapController;
final Set<Marker> _markers = {};
Set<Polyline> _polylines = {};
LatLng? _currentLocation;
LatLng? _destination;
bool _isGettingLocation = true;
bool _isRequestingPermission = true;
final TextEditingController _searchController = TextEditingController();
List<Map<String, dynamic>> _placeSuggestions = [];
bool _isSearching = false;
@override
void initState() {
super.initState();
_requestLocationPermission();
}
Future<void> _requestLocationPermission() async {
try {
print('🔐 Solicitando permissões de localização...');
// Verificar status atual das permissões
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.locationWhenInUse,
Permission.locationAlways,
].request();
bool locationGranted =
(statuses[Permission.location]?.isGranted == true) ||
(statuses[Permission.locationWhenInUse]?.isGranted == true);
if (locationGranted) {
print('✅ Permissões de localização concedidas');
setState(() {
_isRequestingPermission = false;
});
_getCurrentLocation();
} else {
print('❌ Permissões de localização negadas');
setState(() {
_isRequestingPermission = false;
});
_showLocationPermissionDialog();
}
} catch (e) {
print('❌ Erro ao solicitar permissões: $e');
_showSnackBar('Erro ao solicitar permissões de localização', Colors.red);
}
}
void _showLocationPermissionDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: AppColors.backgroundGrey,
title: const Text(
'Permissão de Localização',
style: TextStyle(
color: AppColors.white,
fontWeight: FontWeight.bold,
),
),
content: const Text(
'Este aplicativo precisa acessar sua localização para mostrar rotas de caminhada e calcular distâncias. Por favor, habilite a localização nas configurações do seu dispositivo.',
style: TextStyle(color: AppColors.white),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
_openAppSettings();
},
child: const Text(
'Abrir Configurações',
style: TextStyle(color: AppColors.coral),
),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop(); // Volta para a tela anterior
},
child: const Text(
'Cancelar',
style: TextStyle(color: AppColors.white),
),
),
],
);
},
);
}
Future<void> _openAppSettings() async {
try {
await openAppSettings();
} catch (e) {
print('❌ Erro ao abrir configurações: $e');
_showSnackBar('Não foi possível abrir as configurações', Colors.red);
}
}
Future<void> _getCurrentLocation() async {
try {
print('🔍 Iniciando busca de localização...');
// Verificar permissões de localização
LocationPermission permission = await Geolocator.checkPermission();
print('📍 Permissão atual: $permission');
if (permission == LocationPermission.denied) {
print('❌ Permissão negada, solicitando...');
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
_showSnackBar(
'Permissão de localização negada. Habilite nas configurações.',
AppColors.coral,
);
return;
}
}
if (permission == LocationPermission.deniedForever) {
_showSnackBar(
'Permissão de localização permanentemente negada. Habilite nas configurações do app.',
AppColors.coral,
);
return;
}
// Obter localização atual
print('🛰️ Buscando localização atual...');
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 10),
);
print(
'✅ Localização encontrada: ${position.latitude}, ${position.longitude}',
);
setState(() {
_currentLocation = LatLng(position.latitude, position.longitude);
_isGettingLocation = false;
});
// Adicionar marcador da localização atual
_addCurrentLocationMarker();
// Mover câmera para localização atual
if (mounted && _currentLocation != null) {
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(target: _currentLocation!, zoom: 15.0),
),
);
}
} catch (e) {
print('❌ Erro ao obter localização: $e');
setState(() {
_isGettingLocation = false;
});
_showSnackBar('Erro ao obter localização: ${e.toString()}', Colors.red);
}
}
void _addCurrentLocationMarker() {
if (_currentLocation != null) {
print('📍 Adicionando marcador de localização atual');
// Remover marcador anterior se existir
_markers.removeWhere(
(marker) => marker.markerId.value == 'current_location',
);
// Adicionar novo marcador
_markers.add(
Marker(
markerId: const MarkerId('current_location'),
position: _currentLocation!,
infoWindow: const InfoWindow(title: 'Sua Localização'),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
),
);
setState(() {});
}
}
Future<void> _searchPlaces(String query) async {
if (query.isEmpty) {
setState(() {
_placeSuggestions = [];
});
return;
}
setState(() {
_isSearching = true;
});
try {
// Usar Google Places API para busca real baseada na localização atual
final String url =
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
'?input=${Uri.encodeComponent(query)}'
'&location=${_currentLocation?.latitude ?? -23.5505},${_currentLocation?.longitude ?? -46.6333}'
'&radius=30000' // 30km de raio
'&language=pt_BR'
'&region=br'
'&components=country:br'
'&strictbounds=true' // Força busca dentro da área
'&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k';
print('🔍 Buscando lugares: $url');
print(
'📍 Baseado na localização: ${_currentLocation?.latitude ?? -23.5505}, ${_currentLocation?.longitude ?? -46.6333}',
);
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK') {
final predictions = data['predictions'] as List;
setState(() {
_placeSuggestions = predictions.map((prediction) {
return {
'description': prediction['description'],
'place_id': prediction['place_id'],
'structured_formatting': prediction['structured_formatting'],
};
}).toList();
});
print(
'${_placeSuggestions.length} lugares encontrados próximos à sua localização',
);
} else {
print(
'❌ Erro na API: ${data['status']} - ${data['error_message'] ?? ''}',
);
}
} else {
print('❌ Erro HTTP: ${response.statusCode}');
}
} catch (e) {
print('❌ Erro na busca: $e');
} finally {
setState(() {
_isSearching = false;
});
}
}
Future<void> _selectPlace(Map<String, dynamic> place) async {
try {
print('📍 Selecionado: ${place['description']}');
// Obter detalhes do lugar
final String detailsUrl =
'https://maps.googleapis.com/maps/api/place/details/json'
'?place_id=${place['place_id']}'
'&fields=geometry'
'&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k';
final response = await http.get(Uri.parse(detailsUrl));
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK') {
final location = data['result']['geometry']['location'];
final destination = LatLng(location['lat'], location['lng']);
setState(() {
_destination = destination;
_placeSuggestions = []; // Limpar sugestões
});
_addDestinationMarker(destination, place['description']);
await _getDirections();
}
}
} catch (e) {
print('❌ Erro ao selecionar lugar: $e');
_showSnackBar('Erro ao selecionar destino', Colors.red);
}
}
void _addDestinationMarker(LatLng location, String name) {
print('📍 Adicionando marcador de destino: $name');
setState(() {
// Remover marcador de destino anterior se existir
_markers.removeWhere((marker) => marker.markerId.value == 'destination');
// Adicionar novo marcador de destino
_markers.add(
Marker(
markerId: const MarkerId('destination'),
position: location,
infoWindow: InfoWindow(title: name),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure,
),
),
);
});
print('✅ Marcador de destino adicionado: $name');
}
Future<void> _getDirections() async {
if (_currentLocation == null || _destination == null) {
print('❌ Localização ou destino nulo');
return;
}
try {
print('🛣️ Buscando rota real com Google Directions API...');
// Usar Google Directions API para obter rota realista
final String url =
'https://maps.googleapis.com/maps/api/directions/json'
'?origin=${_currentLocation!.latitude},${_currentLocation!.longitude}'
'&destination=${_destination!.latitude},${_destination!.longitude}'
'&mode=walking'
'&language=pt_BR'
'&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k';
print('🌐 URL da requisição: $url');
final response = await http.get(Uri.parse(url));
print('📡 Status code: ${response.statusCode}');
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK' && data['routes'].isNotEmpty) {
final route = data['routes'][0];
final legs = route['legs'][0];
final steps = legs['steps'];
print('✅ Rota encontrada com ${steps.length} passos');
// Extrair pontos da rota
final List<LatLng> routePoints = [];
// Adicionar ponto inicial
routePoints.add(_currentLocation!);
// Processar cada passo da rota
for (var step in steps) {
final startLocation = step['start_location'];
final endLocation = step['end_location'];
if (startLocation != null) {
routePoints.add(
LatLng(startLocation['lat'], startLocation['lng']),
);
}
if (endLocation != null) {
routePoints.add(LatLng(endLocation['lat'], endLocation['lng']));
}
}
// Adicionar ponto final
routePoints.add(_destination!);
print('📍 Total de pontos na rota: ${routePoints.length}');
setState(() {
_polylines.clear();
_polylines.add(
Polyline(
polylineId: const PolylineId('route'),
color: AppColors.backgroundGrey,
width: 5,
points: routePoints,
),
);
});
// Ajustar câmera para mostrar toda a rota
_adjustCameraToShowRoute();
// Calcular distância e tempo
final distance = legs['distance']['text'] ?? 'Calculando...';
final duration = legs['duration']['text'] ?? 'Calculando...';
print('📊 Distância: $distance, Tempo: $duration');
_showSnackBar(
'Rota de caminhada calculada!\nDistância: $distance\nTempo: $duration',
Colors.green,
);
} else {
print(
'❌ Erro na resposta: ${data['status']} - ${data['error_message']}',
);
_showSnackBar(
'Não foi possível calcular a rota. Tente novamente.',
AppColors.coral,
);
}
} else {
print('❌ Erro HTTP: ${response.statusCode}');
_showSnackBar(
'Erro ao conectar com o servidor. Verifique sua internet.',
Colors.red,
);
}
} catch (e) {
print('❌ Erro ao calcular rota: $e');
_showSnackBar('Erro ao calcular rota: ${e.toString()}', Colors.red);
}
}
void _adjustCameraToShowRoute() {
if (_currentLocation == null || _destination == null) return;
final double padding = 0.01; // Padding para mostrar os marcadores
final bounds = LatLngBounds(
southwest: LatLng(
(_currentLocation!.latitude < _destination!.latitude
? _currentLocation!.latitude
: _destination!.latitude) -
padding,
(_currentLocation!.longitude < _destination!.longitude
? _currentLocation!.longitude
: _destination!.longitude) -
padding,
),
northeast: LatLng(
(_currentLocation!.latitude > _destination!.latitude
? _currentLocation!.latitude
: _destination!.latitude) +
padding,
(_currentLocation!.longitude > _destination!.longitude
? _currentLocation!.longitude
: _destination!.longitude) +
padding,
),
);
print('📷 Ajustando câmera para mostrar rota completa');
mapController.animateCamera(CameraUpdate.newLatLngBounds(bounds, 100));
}
void _onMapCreated(GoogleMapController controller) {
print('🗺️ Mapa criado');
mapController = controller;
}
void _showSnackBar(String message, Color color) {
print('💬 Mostrando mensagem: $message');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: color,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
),
);
}
void _clearRoute() {
print('🧹 Limpando rota...');
setState(() {
_destination = null;
_polylines.clear();
_markers.removeWhere((marker) => marker.markerId.value == 'destination');
});
_showSnackBar('Rota limpa. Busque um novo destino.', AppColors.coral);
}
void _centerOnCurrentLocation() {
if (_currentLocation != null) {
print('🎯 Centralizando na localização atual');
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(target: _currentLocation!, zoom: 15.0),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.backgroundGrey.withOpacity(0.3),
AppColors.background,
],
),
),
child: Stack(
children: [
// Back button
Positioned(
top: 50,
left: 20,
child: Container(
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.1)),
),
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
},
tooltip: 'Voltar',
),
),
),
Column(
children: [
const SizedBox(height: 100),
// Search section
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Buscar Destino:',
style: TextStyle(
color: AppColors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
// Search bar
Container(
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.2),
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: AppColors.backgroundGrey.withOpacity(0.3),
),
),
child: TextField(
controller: _searchController,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
enableSuggestions: true,
autocorrect: true,
decoration: InputDecoration(
hintText:
'Ex: Parque Ibirapuera, Avenida Paulista...',
hintStyle: TextStyle(
color: AppColors.white.withOpacity(0.5),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
prefixIcon: const Icon(
Icons.search,
color: AppColors.white,
),
suffixIcon: _isSearching
? const Padding(
padding: EdgeInsets.all(12),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: AppColors.backgroundGrey,
strokeWidth: 2,
),
),
)
: IconButton(
icon: const Icon(
Icons.send,
color: AppColors.backgroundGrey,
),
onPressed: () =>
_searchPlaces(_searchController.text),
),
),
style: const TextStyle(color: AppColors.white),
onChanged: (value) {
if (value.length >= 3) {
_searchPlaces(value);
} else {
setState(() {
_placeSuggestions = [];
});
}
},
onSubmitted: (value) {
if (_placeSuggestions.isNotEmpty) {
_selectPlace(_placeSuggestions.first);
}
},
),
),
// Suggestions dropdown
if (_placeSuggestions.isNotEmpty)
Container(
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.2),
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: AppColors.backgroundGrey.withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _placeSuggestions.take(5).map((place) {
return InkWell(
onTap: () => _selectPlace(place),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Row(
children: [
const Icon(
Icons.place,
color: AppColors.backgroundGrey,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Text(
place['description'],
style: const TextStyle(
color: AppColors.white,
fontSize: 14,
),
),
),
],
),
),
);
}).toList(),
),
),
],
),
),
const SizedBox(height: 20),
// Status bar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
children: [
Text(
_isRequestingPermission
? "Solicitando permissões..."
: _isGettingLocation
? "Obtendo localização..."
: _destination != null
? "Rota ativa"
: "Defina um destino",
style: const TextStyle(
color: AppColors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
if (_isRequestingPermission)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: const Text(
"PERMISSÃO",
style: TextStyle(
color: AppColors.backgroundGrey,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
)
else if (_destination != null)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: const Text(
"ATIVO",
style: TextStyle(
color: AppColors.backgroundGrey,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const SizedBox(height: 15),
// Map
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.backgroundGrey.withOpacity(0.3),
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target:
_currentLocation ??
const LatLng(-23.5505, -46.6333),
zoom: 15.0,
),
markers: _markers,
polylines: _polylines,
mapType: MapType.normal,
myLocationEnabled: true,
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
),
// Loading indicator
if (_isRequestingPermission || _isGettingLocation)
Container(
color: Colors.black54,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(
color: AppColors.backgroundGrey,
strokeWidth: 3,
),
const SizedBox(height: 16),
Text(
_isRequestingPermission
? 'Solicitando permissões de localização...'
: 'Obtendo sua localização...',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
),
],
),
),
),
// Map controls
Positioned(
top: 20,
right: 20,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(
0.8,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withOpacity(0.1),
),
),
child: Column(
children: [
IconButton(
icon: const Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
mapController.animateCamera(
CameraUpdate.zoomIn(),
);
},
tooltip: 'Aumentar zoom',
),
IconButton(
icon: const Icon(
Icons.remove,
color: Colors.white,
),
onPressed: () {
mapController.animateCamera(
CameraUpdate.zoomOut(),
);
},
tooltip: 'Diminuir zoom',
),
IconButton(
icon: const Icon(
Icons.my_location,
color: Colors.white,
),
onPressed: _centerOnCurrentLocation,
tooltip: 'Minha localização',
),
IconButton(
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: _clearRoute,
tooltip: 'Limpar rota',
),
],
),
),
],
),
),
],
),
),
),
),
const SizedBox(height: 20),
],
),
],
),
),
);
}
}

View File

@@ -5,101 +5,6 @@ import '../sheets/entrar_sheet.dart';
import '../sheets/registrar_sheet.dart'; import '../sheets/registrar_sheet.dart';
import '../services/supabase_service.dart'; import '../services/supabase_service.dart';
class AnimatedButton extends StatefulWidget {
final String text;
final VoidCallback onPressed;
final Color backgroundColor;
final Color textColor;
const AnimatedButton({
super.key,
required this.text,
required this.onPressed,
required this.backgroundColor,
required this.textColor,
});
@override
State<AnimatedButton> createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _bounceAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.92,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
_bounceAnimation = Tween<double>(begin: 0.0, end: -3.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.8, curve: Curves.elasticOut),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
_controller.forward().then((_) {
_controller.reverse().then((_) {
widget.onPressed();
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _bounceAnimation.value),
child: Transform.scale(
scale: _scaleAnimation.value,
child: SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton(
onPressed: _handleTap,
style: ElevatedButton.styleFrom(
backgroundColor: widget.backgroundColor,
foregroundColor: widget.textColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: Text(
widget.text,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
),
);
},
);
}
}
class InicialScreen extends StatelessWidget { class InicialScreen extends StatelessWidget {
const InicialScreen({super.key}); const InicialScreen({super.key});
@@ -119,32 +24,64 @@ class InicialScreen extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
AnimatedButton( SizedBox(
text: AppStrings.registrar, width: double.infinity,
onPressed: () { height: 60,
showModalBottomSheet( child: ElevatedButton(
context: context, onPressed: () {
isScrollControlled: true, showModalBottomSheet(
backgroundColor: Colors.transparent, context: context,
builder: (context) => const RegistrarSheet(), isScrollControlled: true,
); backgroundColor: Colors.transparent,
}, builder: (context) => const RegistrarSheet(),
backgroundColor: AppColors.buttonColor, );
textColor: AppColors.white, },
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: AppColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: Text(
AppStrings.registrar,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
AnimatedButton( SizedBox(
text: AppStrings.entrar, width: double.infinity,
onPressed: () { height: 60,
showModalBottomSheet( child: ElevatedButton(
context: context, onPressed: () {
isScrollControlled: true, showModalBottomSheet(
backgroundColor: Colors.transparent, context: context,
builder: (context) => const EntrarSheet(), isScrollControlled: true,
); backgroundColor: Colors.transparent,
}, builder: (context) => const EntrarSheet(),
backgroundColor: AppColors.buttonColor, );
textColor: AppColors.white, },
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: AppColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: Text(
AppStrings.entrar,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
), ),
], ],
), ),

View File

@@ -1,652 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../constants/app_colors.dart';
import '../services/supabase_service.dart';
import '../screens/inicial_screen.dart';
import '../screens/google_maps_screen.dart';
import '../screens/setting_screen.dart';
/// Tela principal para usuários logados com estatísticas de corrida e menu.
class LogadoInicialScreen extends StatefulWidget {
const LogadoInicialScreen({super.key});
@override
State<LogadoInicialScreen> createState() => _LogadoInicialScreenState();
}
class _LogadoInicialScreenState extends State<LogadoInicialScreen>
with SingleTickerProviderStateMixin {
// Variáveis de estado para controlar os dados da corrida.
double progress = 0.0; // Progresso de 0.0 a 1.0 (ex: 0.5 = 50%).
double targetDistance = 8.0; // Distância alvo em KM.
double currentDistance = 0.0; // Distância atual percorrida.
@override
void initState() {
super.initState();
// Simular progresso inicial
_simulateProgress();
}
void _simulateProgress() {
Timer.periodic(const Duration(seconds: 3), (timer) {
if (mounted && progress < 1.0) {
setState(() {
progress = (progress + 0.1).clamp(0.0, 1.0);
currentDistance = targetDistance * progress;
});
} else {
timer.cancel();
}
});
}
/// Constrói o indicador de progresso circular central.
Widget _buildCircularProgressIndicator() {
return SizedBox(
width: 200,
height: 200,
child: Stack(
alignment: Alignment.center,
children: [
// TweenAnimationBuilder cria uma animação suave quando o valor do progresso muda.
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: progress),
duration: const Duration(milliseconds: 500),
builder: (context, value, _) {
return CustomPaint(
size: const Size(200, 200),
painter: CircularProgressPainter(
progress: value,
strokeWidth: 12,
progressColor: AppColors.white,
backgroundColor: AppColors.white.withOpacity(0.3),
),
);
},
),
// Círculo interno cinza que contém o texto da porcentagem.
Container(
width: 170,
height: 170,
decoration: const BoxDecoration(
color: AppColors.backgroundGrey,
shape: BoxShape.circle,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${(progress * 100).toInt()}%",
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Text(
"COMPLETO",
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final user = SupabaseService.currentUser;
final userName =
user?.userMetadata?['name'] ?? user?.email?.split('@')[0] ?? 'Usuário';
return Scaffold(
backgroundColor: AppColors.background,
body: Stack(
children: [
// Static dark gray triangles
Positioned(
top: -50,
left: -80,
child: CustomPaint(
size: const Size(160, 120),
painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.4),
),
),
),
Positioned(
top: 20,
right: -60,
child: CustomPaint(
size: const Size(120, 90),
painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.3),
),
),
),
Positioned(
top: 80,
left: 40,
child: CustomPaint(
size: const Size(140, 105),
painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.35),
),
),
),
Positioned(
top: 120,
right: 80,
child: CustomPaint(
size: const Size(100, 75),
painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.25),
),
),
),
Positioned(
top: 160,
left: -40,
child: CustomPaint(
size: const Size(130, 98),
painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.3),
),
),
),
// User info header
Positioned(
top: 40,
left: 20,
right: 20,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Olá, $userName!',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'Bem-vindo de volta!',
style: TextStyle(color: Colors.white70, fontSize: 14),
),
],
),
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: AppColors.buttonColor,
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.person,
color: AppColors.white,
size: 24,
),
),
],
),
),
// 1. Indicador de progresso circular posicionado no topo central.
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 140),
child: _buildCircularProgressIndicator(),
),
),
// 2. Exibição da distância (ex: 0.0 KM | 8.0 KM).
Positioned(
top: 360,
left: 0,
right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
decoration: BoxDecoration(
color: AppColors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
"${currentDistance.toStringAsFixed(1)} KM | ${targetDistance.toStringAsFixed(1)} KM",
style: const TextStyle(
color: AppColors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
),
// 3. Contêiner de estatísticas (Passos, BPM, K/CAL) e o mapa clicável.
Positioned(
top: 420,
left: 20,
right: 20,
child: Container(
height: 200,
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(24),
),
child: Row(
children: [
// Coluna esquerda com ícones e valores de estatística.
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem(
Icons.directions_run,
"3219",
"PASSOS",
),
const Divider(color: Colors.white24, height: 1),
_buildStatItem(Icons.favorite_border, "98", "BPM"),
const Divider(color: Colors.white24, height: 1),
_buildStatItem(
Icons.local_fire_department,
"480",
"K/CAL",
),
],
),
),
),
// Coluna direita contendo o mapa clicável.
Expanded(
flex: 6,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const GoogleMapScreen(),
),
);
},
child: ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(24),
bottomRight: Radius.circular(24),
),
child: Stack(
children: [
Container(
color: const Color(0xFF3A3A3C),
), // Fundo do mapa.
CustomPaint(
size: Size.infinite,
painter:
MapPainter(), // Desenha as linhas e o marcador.
),
// Overlay para indicar que é clicável
Container(
color: Colors.black.withOpacity(0.1),
child: const Center(
child: Icon(
Icons.touch_app,
color: Colors.white54,
size: 40,
),
),
),
],
),
),
),
),
],
),
),
),
// 4. Barra de progresso linear (centralizada acima dos botões inferiores).
Positioned(
bottom: 160,
left:
40, // Espaçamento igual na esquerda e direita para centralizar.
right: 40,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: LinearProgressIndicator(
value: progress,
minHeight: 12,
backgroundColor: AppColors.backgroundGrey.withOpacity(0.3),
valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.white,
),
),
),
),
// 5. Botões de menu inferiores.
Positioned(
bottom: 60,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildMenuButton(
Icons.settings,
'Configurações',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsScreen(),
),
);
},
),
_buildMenuButton(Icons.group_outlined, 'Grupos'),
_buildMenuButton(Icons.access_time, 'Histórico'),
_buildMenuButton(
Icons.notifications_none,
'Notificações',
showBadge: true,
),
_buildMenuButton(Icons.logout, 'Sair', isLogout: true),
],
),
),
// 6. Botão de Bluetooth no canto superior direito.
Positioned(
top: 40,
right: 30,
child: GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Bluetooth clicado!'),
duration: Duration(seconds: 1),
),
);
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: AppColors.backgroundGrey,
shape: BoxShape.circle,
),
child: Stack(
children: [
const Icon(
Icons.bluetooth,
color: AppColors.white,
size: 20,
),
// Pontinho vermelho indicando status ou notificação.
Positioned(
left: 0,
bottom: 0,
child: Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
],
),
),
),
),
],
),
);
}
/// Constrói uma linha de estatística com ícone, valor e rótulo.
Widget _buildStatItem(IconData icon, String value, String label) {
return Row(
children: [
Icon(icon, color: Colors.white70, size: 24),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: const TextStyle(color: Colors.white60, fontSize: 10),
),
],
),
],
);
}
/// Constrói um botão de menu clicável.
Widget _buildMenuButton(
IconData icon,
String message, {
bool showBadge = false,
bool isLogout = false,
VoidCallback? onTap,
}) {
return GestureDetector(
onTap:
onTap ??
() async {
if (isLogout) {
await SupabaseService.signOut();
if (mounted) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const InicialScreen(),
),
(route) => false,
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$message clicado!'),
duration: const Duration(seconds: 1),
),
);
}
},
child: Stack(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: isLogout ? Colors.red.shade600 : AppColors.backgroundGrey,
shape: BoxShape.circle,
),
child: Icon(icon, color: AppColors.white, size: 24),
),
// Exibe um pontinho vermelho de notificação se showBadge for true.
if (showBadge)
Positioned(
left: 0,
bottom: 0,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
],
),
);
}
}
/// Pintor customizado para desenhar os triângulos estáticos.
class TrianglePainter extends CustomPainter {
final Color color;
TrianglePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final path = Path();
path.moveTo(size.width / 2, 0);
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
/// Pintor customizado para desenhar o traçado do mapa simulado.
class MapPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white38
..strokeWidth = 3
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
// Desenha a linha sinuosa do percurso.
final path = Path();
path.moveTo(size.width * 0.1, size.height * 0.8);
path.quadraticBezierTo(
size.width * 0.3,
size.height * 0.7,
size.width * 0.4,
size.height * 0.4,
);
path.quadraticBezierTo(
size.width * 0.5,
size.height * 0.1,
size.width * 0.7,
size.height * 0.3,
);
path.lineTo(size.width * 0.9, size.height * 0.2);
// Desenha uma "estrada" mais grossa branca.
final roadPaint = Paint()
..color = Colors.white
..strokeWidth = 8
..style = PaintingStyle.stroke;
final roadPath = Path();
roadPath.moveTo(size.width * 0.6, size.height * 1.1);
roadPath.quadraticBezierTo(
size.width * 0.7,
size.height * 0.8,
size.width * 1.1,
size.height * 0.7,
);
canvas.drawPath(path, paint);
canvas.drawPath(roadPath, roadPaint);
// Desenha o marcador circular (o pino no mapa).
final markerPaint = Paint()..color = const Color(0xFFFF6B6B);
final markerPos = Offset(size.width * 0.4, size.height * 0.4);
canvas.drawCircle(markerPos, 6, markerPaint);
// Desenha o centro branco do marcador.
final innerPaint = Paint()..color = Colors.white;
canvas.drawCircle(markerPos, 2, innerPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
/// Pintor customizado para desenhar o arco de progresso circular.
class CircularProgressPainter extends CustomPainter {
final double progress;
final double strokeWidth;
final Color progressColor;
final Color backgroundColor;
CircularProgressPainter({
required this.progress,
required this.strokeWidth,
required this.progressColor,
required this.backgroundColor,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2;
// Desenha o círculo de fundo (cinza transparente).
final backgroundPaint = Paint()
..color = backgroundColor
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
canvas.drawCircle(center, radius, backgroundPaint);
// Desenha o arco de progresso (branco).
final progressPaint = Paint()
..color = progressColor
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
const startAngle = -3.14159265359 / 2; // Começa no topo (-90 graus).
final sweepAngle =
2 *
3.14159265359 *
progress; // Define o tamanho do arco com base no progresso.
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
startAngle,
sweepAngle,
false,
progressPaint,
);
}
@override
bool shouldRepaint(CircularProgressPainter oldDelegate) =>
oldDelegate.progress != progress;
}

View File

@@ -1,24 +1,34 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../constants/app_colors.dart'; import '../constants/app_colors.dart';
import '../screens/inicial_screen.dart'; import '../constants/app_strings.dart';
import '../screens/setting_screen.dart';
import '../screens/bluetooth_connection_screen.dart';
import '../screens/google_maps_screen.dart';
class LogadoScreen extends StatelessWidget { class LogadoScreen extends StatefulWidget {
const LogadoScreen({super.key}); const LogadoScreen({super.key});
@override
State<LogadoScreen> createState() => _LogadoScreenState();
}
class _LogadoScreenState extends State<LogadoScreen> {
double progress = 0.65; // Simulação de progresso
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
body: Stack( body: Stack(
children: [ children: [
// Static dark gray triangles // Background triangles
Positioned( Positioned(
top: -50, top: -50,
left: -80, left: -80,
child: CustomPaint( child: CustomPaint(
size: const Size(160, 120), size: const Size(160, 120),
painter: TrianglePainter( painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.4), color: Colors.grey.shade800.withValues(alpha: 0.4),
), ),
), ),
), ),
@@ -28,7 +38,7 @@ class LogadoScreen extends StatelessWidget {
child: CustomPaint( child: CustomPaint(
size: const Size(120, 90), size: const Size(120, 90),
painter: TrianglePainter( painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.3), color: Colors.grey.shade800.withAlpha(76),
), ),
), ),
), ),
@@ -38,7 +48,7 @@ class LogadoScreen extends StatelessWidget {
child: CustomPaint( child: CustomPaint(
size: const Size(140, 105), size: const Size(140, 105),
painter: TrianglePainter( painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.35), color: Colors.grey.shade800.withAlpha(89),
), ),
), ),
), ),
@@ -48,7 +58,7 @@ class LogadoScreen extends StatelessWidget {
child: CustomPaint( child: CustomPaint(
size: const Size(100, 75), size: const Size(100, 75),
painter: TrianglePainter( painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.25), color: Colors.grey.shade800.withAlpha(51),
), ),
), ),
), ),
@@ -58,7 +68,7 @@ class LogadoScreen extends StatelessWidget {
child: CustomPaint( child: CustomPaint(
size: const Size(130, 98), size: const Size(130, 98),
painter: TrianglePainter( painter: TrianglePainter(
color: Colors.grey.shade800.withOpacity(0.3), color: Colors.grey.shade800.withAlpha(76),
), ),
), ),
), ),
@@ -68,127 +78,329 @@ class LogadoScreen extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox(height: 80), // Space for triangles const SizedBox(height: 60),
// Success icon with animation
TweenAnimationBuilder(
duration: const Duration(milliseconds: 600),
tween: Tween<double>(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: AppColors.buttonColor,
borderRadius: BorderRadius.circular(50),
boxShadow: [
BoxShadow(
color: AppColors.buttonColor.withOpacity(0.3),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: const Icon(
Icons.check,
size: 50,
color: AppColors.white,
),
),
);
},
),
const SizedBox(height: 32),
// Welcome message with fade in // Header with user info
TweenAnimationBuilder( Row(
duration: const Duration(milliseconds: 800), mainAxisAlignment: MainAxisAlignment.spaceBetween,
tween: Tween<double>(begin: 0.0, end: 1.0), children: [
builder: (context, value, child) { Column(
return Opacity( crossAxisAlignment: CrossAxisAlignment.start,
opacity: value, children: [
child: const Text( const Text(
'Login Realizado!', 'Bem-vindo!',
style: TextStyle( style: TextStyle(
fontSize: 32, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,
),
textAlign: TextAlign.center,
),
);
},
),
const SizedBox(height: 16),
TweenAnimationBuilder(
duration: const Duration(milliseconds: 1000),
tween: Tween<double>(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: const Text(
'Você está autenticado com sucesso.',
style: TextStyle(fontSize: 18, color: Colors.white70),
textAlign: TextAlign.center,
),
);
},
),
const SizedBox(height: 48),
// Back button with slide up animation
TweenAnimationBuilder(
duration: const Duration(milliseconds: 1200),
tween: Tween<Offset>(
begin: Offset(0, 1),
end: Offset(0, 0),
),
builder: (context, value, child) {
return Transform.translate(
offset: value * 50,
child: SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const InicialScreen(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: AppColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
), ),
child: const Text( ),
'Voltar para Tela Inicial', Text(
'Usuário Logado',
style: TextStyle(
fontSize: 16,
color: Colors.white.withValues(alpha: 0.7),
),
),
],
),
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: AppColors.buttonColor,
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.person,
color: Colors.white,
size: 30,
),
),
],
),
const SizedBox(height: 40),
// Stats cards
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatCard(
AppStrings.complete,
'85%',
AppColors.success,
),
_buildStatCard(
AppStrings.steps,
'8,432',
AppColors.buttonColor,
),
_buildStatCard(AppStrings.bpm, '72', AppColors.error),
_buildStatCard(AppStrings.kcal, '420', AppColors.coral),
],
),
const SizedBox(height: 30),
// Progress section
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(15),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Meta Diária',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.bold,
color: Colors.white,
), ),
), ),
Text(
'${(progress * 100).toInt()}%',
style: const TextStyle(
fontSize: 16,
color: Colors.white,
),
),
],
),
const SizedBox(height: 15),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
value: progress,
minHeight: 8,
backgroundColor: AppColors.white.withValues(
alpha: 0.1,
),
valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.white,
),
), ),
), ),
],
),
),
const SizedBox(height: 30),
// Map preview
GestureDetector(
onTap: () {
// Navigate to GoogleMapScreen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const GoogleMapScreen(),
),
); );
}, },
child: Container(
height: 200,
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(15),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.map_outlined,
size: 50,
color: Colors.white54,
),
SizedBox(height: 10),
Text(
AppStrings.mapPreview,
style: TextStyle(
color: Colors.white54,
fontSize: 16,
),
),
],
),
),
),
), ),
], ],
), ),
), ),
), ),
// Bottom navigation menu
Positioned(
bottom: 50,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildMenuButton(Icons.settings_outlined, AppStrings.settings),
_buildMenuButton(Icons.group_outlined, AppStrings.groups),
_buildMenuButton(Icons.history_rounded, AppStrings.history),
_buildMenuButton(
Icons.notifications_none_rounded,
AppStrings.notifications,
showBadge: true,
),
_buildMenuButton(
Icons.person_outline_rounded,
AppStrings.profile,
isAvatar: true,
),
],
),
),
// Bluetooth action button
Positioned(
top: 60,
right: 25,
child: _buildSmallActionButton(Icons.bluetooth, AppColors.error),
),
], ],
), ),
); );
} }
Widget _buildStatCard(String title, String value, Color color) {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 5),
Text(
title,
style: const TextStyle(fontSize: 12, color: Colors.white70),
),
],
),
);
}
Widget _buildMenuButton(
IconData icon,
String label, {
bool showBadge = false,
bool isAvatar = false,
}) {
return GestureDetector(
onTap: () {
_handleMenuTap(label);
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(25),
),
child: Stack(
children: [
Center(child: Icon(icon, color: Colors.white, size: 24)),
if (showBadge)
Positioned(
top: 8,
right: 8,
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: AppColors.error,
borderRadius: BorderRadius.circular(4),
),
),
),
],
),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 12, color: Colors.white70),
),
],
),
);
}
Widget _buildSmallActionButton(IconData icon, Color color) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BluetoothConnectionScreen(),
),
);
},
child: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(22.5),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.3),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Icon(icon, color: Colors.white, size: 20),
),
);
}
void _handleMenuTap(String label) {
switch (label) {
case 'Configurações':
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingsScreen()),
);
break;
case 'Perfil':
// Navigate to profile
break;
case 'Histórico':
// Navigate to history
break;
case 'Grupos':
// Navigate to groups
break;
case 'Notificações':
// Navigate to notifications
break;
}
}
} }
// Custom painter for triangles // Custom painter for triangles

View File

@@ -82,7 +82,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Text( Text(
userEmail, userEmail,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.7), color: Colors.white.withValues(alpha: 0.7),
fontSize: 14, fontSize: 14,
), ),
), ),
@@ -127,12 +127,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
title: 'Modo Noturno', title: 'Modo Noturno',
trailing: Switch( trailing: Switch(
value: _isNightMode, value: _isNightMode,
activeThumbColor: AppColors.buttonColor,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_isNightMode = value; _isNightMode = value;
}); });
}, },
activeColor: AppColors.buttonColor,
), ),
), ),
_buildDivider(), _buildDivider(),
@@ -142,7 +142,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
trailing: Text( trailing: Text(
_selectedLanguage, _selectedLanguage,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.7), color: Colors.white.withValues(alpha: 0.7),
fontSize: 16, fontSize: 16,
), ),
), ),
@@ -174,7 +174,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_notificationsEnabled = value; _notificationsEnabled = value;
}); });
}, },
activeColor: AppColors.buttonColor, activeThumbColor: AppColors.buttonColor,
), ),
), ),
_buildDivider(), _buildDivider(),
@@ -218,7 +218,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Logout Button // Logout Button
Container( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
@@ -270,7 +270,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Widget _buildDivider() { Widget _buildDivider() {
return Divider( return Divider(
color: Colors.white.withOpacity(0.1), color: AppColors.buttonColor.withValues(alpha: 0.2),
height: 1, height: 1,
indent: 16, indent: 16,
endIndent: 16, endIndent: 16,
@@ -284,7 +284,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
firstDate: DateTime(2020), firstDate: DateTime(2020),
lastDate: DateTime(2025), lastDate: DateTime(2025),
).then((date) { ).then((date) {
if (date != null) { if (date != null && mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Data selecionada: ${date.toString().split(' ')[0]}'), content: Text('Data selecionada: ${date.toString().split(' ')[0]}'),
@@ -326,17 +326,25 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
Widget _buildLanguageOption(String language) { Widget _buildLanguageOption(String language) {
return RadioListTile<String>( return ListTile(
title: Text(language, style: const TextStyle(color: Colors.white)), title: Text(language, style: const TextStyle(color: Colors.white)),
value: language, trailing: Radio<String>(
groupValue: _selectedLanguage, value: language,
onChanged: (value) { groupValue: _selectedLanguage,
onChanged: (value) {
setState(() {
_selectedLanguage = value!;
});
Navigator.pop(context);
},
fillColor: WidgetStateProperty.all(AppColors.buttonColor),
),
onTap: () {
setState(() { setState(() {
_selectedLanguage = value!; _selectedLanguage = language;
}); });
Navigator.pop(context); Navigator.pop(context);
}, },
activeColor: AppColors.buttonColor,
); );
} }

View File

@@ -1,106 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../constants/app_colors.dart'; import '../constants/app_colors.dart';
import '../services/supabase_service.dart'; import '../services/supabase_service.dart';
import '../screens/logado_inicial_screen.dart'; import '../screens/logado_screen.dart';
class AnimatedButton extends StatefulWidget {
final String text;
final VoidCallback onPressed;
final Color backgroundColor;
final Color textColor;
final bool isLoading;
const AnimatedButton({
super.key,
required this.text,
required this.onPressed,
required this.backgroundColor,
required this.textColor,
this.isLoading = false,
});
@override
State<AnimatedButton> createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _bounceAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.92,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
_bounceAnimation = Tween<double>(begin: 0.0, end: -3.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.8, curve: Curves.elasticOut),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
_controller.forward().then((_) {
_controller.reverse().then((_) {
widget.onPressed();
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _bounceAnimation.value),
child: Transform.scale(
scale: _scaleAnimation.value,
child: SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton(
onPressed: widget.isLoading ? null : _handleTap,
style: ElevatedButton.styleFrom(
backgroundColor: widget.backgroundColor,
foregroundColor: widget.textColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: widget.isLoading
? const CircularProgressIndicator(color: Colors.white)
: Text(
widget.text,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
),
);
},
);
}
}
class EntrarSheet extends StatefulWidget { class EntrarSheet extends StatefulWidget {
const EntrarSheet({super.key}); const EntrarSheet({super.key});
@@ -137,11 +38,9 @@ class _EntrarSheetState extends State<EntrarSheet> {
), ),
); );
// Then navigate to LogadoInicialScreen // Navigate to LogadoScreen
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(builder: (context) => const LogadoScreen()),
builder: (context) => const LogadoInicialScreen(),
),
); );
} }
} catch (e) { } catch (e) {
@@ -222,7 +121,7 @@ class _EntrarSheetState extends State<EntrarSheet> {
color: AppColors.backgroundGrey, color: AppColors.backgroundGrey,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, -5), offset: const Offset(0, -5),
), ),
@@ -273,7 +172,7 @@ class _EntrarSheetState extends State<EntrarSheet> {
controller: _emailController, controller: _emailController,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -306,7 +205,7 @@ class _EntrarSheetState extends State<EntrarSheet> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -328,12 +227,31 @@ class _EntrarSheetState extends State<EntrarSheet> {
const SizedBox(height: 32), const SizedBox(height: 32),
// Login button // Login button
AnimatedButton( SizedBox(
text: 'Entrar', width: double.infinity,
onPressed: _handleLogin, height: 60,
backgroundColor: AppColors.buttonColor, child: ElevatedButton(
textColor: AppColors.white, onPressed: _isLoading ? null : _handleLogin,
isLoading: _isLoading, style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: AppColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: _isLoading
? const CircularProgressIndicator(
color: Colors.white,
)
: const Text(
'Entrar',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),

View File

@@ -1,106 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../constants/app_colors.dart';
import '../services/supabase_service.dart'; import '../services/supabase_service.dart';
import '../screens/logado_inicial_screen.dart'; import '../constants/app_colors.dart';
import '../screens/logado_screen.dart';
class AnimatedButton extends StatefulWidget {
final String text;
final VoidCallback onPressed;
final Color backgroundColor;
final Color textColor;
final bool isLoading;
const AnimatedButton({
super.key,
required this.text,
required this.onPressed,
required this.backgroundColor,
required this.textColor,
this.isLoading = false,
});
@override
State<AnimatedButton> createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _bounceAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.92,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
_bounceAnimation = Tween<double>(begin: 0.0, end: -3.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.8, curve: Curves.elasticOut),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
_controller.forward().then((_) {
_controller.reverse().then((_) {
widget.onPressed();
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _bounceAnimation.value),
child: Transform.scale(
scale: _scaleAnimation.value,
child: SizedBox(
width: double.infinity,
height: 60,
child: ElevatedButton(
onPressed: widget.isLoading ? null : _handleTap,
style: ElevatedButton.styleFrom(
backgroundColor: widget.backgroundColor,
foregroundColor: widget.textColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: widget.isLoading
? const CircularProgressIndicator(color: Colors.white)
: Text(
widget.text,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
),
);
},
);
}
}
class RegistrarSheet extends StatefulWidget { class RegistrarSheet extends StatefulWidget {
const RegistrarSheet({super.key}); const RegistrarSheet({super.key});
@@ -144,11 +45,9 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
), ),
); );
// Then navigate to LogadoInicialScreen // Navigate to LogadoScreen
Navigator.of(context).pushReplacement( Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(builder: (context) => const LogadoScreen()),
builder: (context) => const LogadoInicialScreen(),
),
); );
} }
} catch (e) { } catch (e) {
@@ -193,7 +92,7 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
color: AppColors.backgroundGrey, color: AppColors.backgroundGrey,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, -5), offset: const Offset(0, -5),
), ),
@@ -254,7 +153,7 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
controller: _nameController, controller: _nameController,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -285,7 +184,7 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
controller: _emailController, controller: _emailController,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -317,7 +216,7 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -349,7 +248,7 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.white.withOpacity(0.1), fillColor: AppColors.white.withValues(alpha: 0.5),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@@ -371,12 +270,29 @@ class _RegistrarSheetState extends State<RegistrarSheet> {
const SizedBox(height: 32), const SizedBox(height: 32),
// Register button // Register button
AnimatedButton( SizedBox(
text: 'Registrar', width: double.infinity,
onPressed: _handleRegister, height: 60,
backgroundColor: AppColors.buttonColor, child: ElevatedButton(
textColor: AppColors.white, onPressed: _isLoading ? null : _handleRegister,
isLoading: _isLoading, style: ElevatedButton.styleFrom(
backgroundColor: AppColors.buttonColor,
foregroundColor: AppColors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
'Registrar',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
), ),
SizedBox(height: 40), SizedBox(height: 40),
], ],

View File

@@ -6,12 +6,14 @@ import FlutterMacOS
import Foundation import Foundation
import app_links import app_links
import flutter_blue_plus_darwin
import geolocator_apple import geolocator_apple
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.7" version: "4.0.7"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -57,6 +65,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "61a7204381925896a374301498f2f5399e59827c6498ae1e924aaa598751b545"
url: "https://pub.dev"
source: hosted
version: "0.8.3"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -137,6 +153,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.3.1"
dbus:
dependency: transitive
description:
name: dbus
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
url: "https://pub.dev"
source: hosted
version: "0.7.12"
ed25519_edwards: ed25519_edwards:
dependency: transitive dependency: transitive
description: description:
@@ -182,14 +206,70 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_blue_plus:
dependency: "direct main"
description:
name: flutter_blue_plus
sha256: "69a8c87c11fc792e8cf0f997d275484fbdb5143ac9f0ac4d424429700cb4e0ed"
url: "https://pub.dev"
source: hosted
version: "1.36.8"
flutter_blue_plus_android:
dependency: transitive
description:
name: flutter_blue_plus_android
sha256: "6f7fe7e69659c30af164a53730707edc16aa4d959e4c208f547b893d940f853d"
url: "https://pub.dev"
source: hosted
version: "7.0.4"
flutter_blue_plus_darwin:
dependency: transitive
description:
name: flutter_blue_plus_darwin
sha256: "682982862c1d964f4d54a3fb5fccc9e59a066422b93b7e22079aeecd9c0d38f8"
url: "https://pub.dev"
source: hosted
version: "7.0.3"
flutter_blue_plus_linux:
dependency: transitive
description:
name: flutter_blue_plus_linux
sha256: "56b0c45edd0a2eec8f85bd97a26ac3cd09447e10d0094fed55587bf0592e3347"
url: "https://pub.dev"
source: hosted
version: "7.0.3"
flutter_blue_plus_platform_interface:
dependency: transitive
description:
name: flutter_blue_plus_platform_interface
sha256: "84fbd180c50a40c92482f273a92069960805ce324e3673ad29c41d2faaa7c5c2"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
flutter_blue_plus_web:
dependency: transitive
description:
name: flutter_blue_plus_web
sha256: a1aceee753d171d24c0e0cdadb37895b5e9124862721f25f60bb758e20b72c99
url: "https://pub.dev"
source: hosted
version: "7.0.2"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "2.0.3"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -198,14 +278,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.33" version: "2.0.33"
flutter_polyline_points:
dependency: "direct main"
description:
name: flutter_polyline_points
sha256: "3a1c8c30abee9fb0fbe44c70d5d1cedb10ef28ec7ea285c669f02b3e183483aa"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -300,10 +372,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_maps_flutter_android name: google_maps_flutter_android
sha256: "8b569c7abc52bc62d4502bf93847d487c0843f3e6a2a8e122b72e98843b2ab4c" sha256: ba0947315ddc9107ecc8d95fa26eb3b87b4f27b221606ce72518314d99c7306c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.19.1" version: "2.19.2"
google_maps_flutter_ios: google_maps_flutter_ios:
dependency: transitive dependency: transitive
description: description:
@@ -361,7 +433,7 @@ packages:
source: hosted source: hosted
version: "0.15.6" version: "0.15.6"
http: http:
dependency: "direct main" dependency: transitive
description: description:
name: http name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
@@ -376,6 +448,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
intl:
dependency: transitive
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
jwt_decode: jwt_decode:
dependency: transitive dependency: transitive
description: description:
@@ -384,6 +464,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.1" version: "0.3.1"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -412,10 +500,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "2.1.1"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
url: "https://pub.dev"
source: hosted
version: "2.6.2"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -456,6 +560,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -584,6 +696,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.1" version: "0.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -608,6 +728,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
posix: posix:
dependency: transitive dependency: transitive
description: description:
@@ -624,6 +752,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.6.0"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -813,6 +949,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
url_launcher: url_launcher:
dependency: transitive dependency: transitive
description: description:
@@ -925,6 +1069,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -933,6 +1085,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@@ -1,8 +1,6 @@
name: run_vision_pro name: teste_projeto_turma
description: "A new Flutter project." description: "A new Flutter project."
# The following line prevents the package from being accidentally published to publish_to: 'none'
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43 # A version number is three numbers separated by dots, like 1.2.43
@@ -30,28 +28,20 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
flutter_map: ^6.1.0
latlong2: ^0.9.1
google_maps_flutter: ^2.14.2
geolocator: ^10.1.0
flutter_blue_plus: ^1.31.0
permission_handler: ^11.3.1
lottie: ^3.1.2 lottie: ^3.1.2
supabase_flutter: ^2.6.0 supabase_flutter: ^2.6.0
google_maps_flutter: ^2.5.3
geolocator: ^10.1.0
flutter_polyline_points: ^2.0.0
http: ^1.1.0
permission_handler: ^11.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@@ -8,23 +8,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:run_vision_pro/main.dart'; import 'package:teste_projeto_turma/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('App smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(const MyApp()); await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0. // Verify that app loads
expect(find.text('0'), findsOneWidget); expect(find.byType(MaterialApp), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
}); });
} }