atualização
This commit is contained in:
@@ -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!** 🗺️🛣️
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
);
|
);
|
||||||
|
|||||||
0
lib/screens/bluethoot_cpnnection_screen.dart
Normal file
0
lib/screens/bluethoot_cpnnection_screen.dart
Normal file
0
lib/screens/google_map_screen.dart
Normal file
0
lib/screens/google_map_screen.dart
Normal 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'
|
|
||||||
'®ion=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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
190
pubspec.lock
190
pubspec.lock
@@ -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:
|
||||||
|
|||||||
28
pubspec.yaml
28
pubspec.yaml
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user