Merge remote-tracking branch 'origin/main'

# Conflicts:
#	android/app/src/main/AndroidManifest.xml
#	lib/main.dart
#	lib/screens/google_map_screen.dart
#	macos/Flutter/GeneratedPluginRegistrant.swift
#	pubspec.lock
#	pubspec.yaml
#	windows/flutter/generated_plugin_registrant.cc
#	windows/flutter/generated_plugins.cmake
This commit is contained in:
2026-03-05 16:27:19 +00:00
10 changed files with 931 additions and 244 deletions

View File

@@ -3,5 +3,7 @@
the Flutter tool needs it to communicate with the running application the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@@ -1,11 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<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" />

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'constants/app_colors.dart'; import 'constants/app_colors.dart';
import 'screens/google_map_screen.dart'; import 'screens/google_map_screen.dart';
import 'bluetooth_screen.dart'; import 'screens/bluetooth_connection_screen.dart'; // Importando a nova tela
void main() { void main() {
// O ponto de entrada do aplicativo. // Ponto de entrada do aplicativo.
runApp(const MyApp()); runApp(const MyApp());
} }
@@ -34,38 +34,54 @@ class RunningScreen extends StatefulWidget {
class _RunningScreenState extends State<RunningScreen> class _RunningScreenState extends State<RunningScreen>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
// Variáveis de estado para controlar os dados da corrida. // 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 progress = 0.35; // Progresso inicial simulado para estética.
double targetDistance = 8.0; // Distância alvo em KM. double targetDistance = 8.0; // Distância alvo em KM.
double currentDistance = 0.0; // Distância atual percorrida. double currentDistance = 2.8; // Distância atual percorrida simulada.
/// Constrói o indicador de progresso circular central. /// Constrói o indicador de progresso circular central com melhorias estéticas.
Widget _buildCircularProgressIndicator() { Widget _buildCircularProgressIndicator() {
return SizedBox( return SizedBox(
width: 200, width: 210,
height: 200, height: 210,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
// Efeito de brilho (glow) sutil atrás do progresso.
Container(
width: 180,
height: 180,
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColors.white.withOpacity(0.05),
blurRadius: 30,
spreadRadius: 10,
),
],
),
),
// TweenAnimationBuilder cria uma animação suave quando o valor do progresso muda. // TweenAnimationBuilder cria uma animação suave quando o valor do progresso muda.
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: progress), tween: Tween(begin: 0.0, end: progress),
duration: const Duration(milliseconds: 500), duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
builder: (context, value, _) { builder: (context, value, _) {
return CustomPaint( return CustomPaint(
size: const Size(200, 200), size: const Size(210, 210),
painter: CircularProgressPainter( painter: CircularProgressPainter(
progress: value, progress: value,
strokeWidth: 12, strokeWidth: 14,
progressColor: AppColors.white, progressColor: AppColors.white,
backgroundColor: AppColors.white.withOpacity(0.3), backgroundColor: AppColors.white.withOpacity(0.15),
), ),
); );
}, },
), ),
// Círculo interno cinza que contém o texto da porcentagem. // Círculo interno que contém o texto da porcentagem.
Container( Container(
width: 170, width: 175,
height: 170, height: 175,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.backgroundGrey, color: AppColors.backgroundGrey,
shape: BoxShape.circle, shape: BoxShape.circle,
@@ -76,14 +92,20 @@ class _RunningScreenState extends State<RunningScreen>
Text( Text(
"${(progress * 100).toInt()}%", "${(progress * 100).toInt()}%",
style: const TextStyle( style: const TextStyle(
fontSize: 36, fontSize: 42,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w900,
color: Colors.white, color: Colors.white,
letterSpacing: -1,
), ),
), ),
const Text( Text(
"COMPLETO", "COMPLETO",
style: TextStyle(color: Colors.white70, fontSize: 12), style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 11,
fontWeight: FontWeight.bold,
letterSpacing: 1.5,
),
), ),
], ],
), ),
@@ -96,112 +118,142 @@ class _RunningScreenState extends State<RunningScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: backgroundColor: AppColors.background, // Cor de fundo escura definida nas constantes.
AppColors.background, // Cor de fundo escura definida nas constantes.
body: Stack( body: Stack(
children: [ children: [
// 1. Indicador de progresso circular posicionado no topo central. // 1. Indicador de progresso circular posicionado no topo central.
Align( Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 60), padding: const EdgeInsets.only(top: 70),
child: _buildCircularProgressIndicator(), child: _buildCircularProgressIndicator(),
), ),
), ),
// 2. Exibição da distância (ex: 0.0 KM | 8.0 KM). // 2. Exibição da distância estilizada como um badge.
Positioned( Positioned(
top: 280, top: 300,
left: 0, left: 0,
right: 0, right: 0,
child: Center( child: Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 10),
horizontal: 20,
vertical: 8,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white.withOpacity(0.2), color: Colors.white.withOpacity(0.08),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(25),
border: Border.all(color: Colors.white.withOpacity(0.1)),
), ),
child: Text( child: Text(
"${currentDistance.toStringAsFixed(1)} KM | ${targetDistance.toStringAsFixed(1)} KM", "${currentDistance.toStringAsFixed(1)} KM | ${targetDistance.toStringAsFixed(1)} KM",
style: const TextStyle( style: const TextStyle(
color: AppColors.white, color: AppColors.white,
fontSize: 16, fontSize: 15,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w800,
letterSpacing: 0.5,
), ),
), ),
), ),
), ),
), ),
// 3. Contêiner de estatísticas (Passos, BPM, K/CAL) e o mapa simulado. // 3. Contêiner de estatísticas e o mapa interativo.
Positioned( Positioned(
top: 340, top: 360,
left: 20, left: 20,
right: 20, right: 20,
child: Container( child: Container(
height: 200, height: 210,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.backgroundGrey, color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
), ),
child: Row( child: Row(
children: [ children: [
// Coluna esquerda com ícones e valores de estatística. // Lado Esquerdo: Estatísticas.
Expanded( Expanded(
flex: 4, flex: 4,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildStatItem( _buildStatItem(Icons.directions_run_rounded, "3219", "PASSOS"),
Icons.directions_run, Divider(color: Colors.white.withOpacity(0.1), height: 1),
"3219", _buildStatItem(Icons.favorite_rounded, "98", "BPM"),
"PASSOS", Divider(color: Colors.white.withOpacity(0.1), height: 1),
), _buildStatItem(Icons.local_fire_department_rounded, "480", "K/CAL"),
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 desenho do mapa simulado. // Lado Direito: Miniatura do Mapa Clicável.
Expanded( Expanded(
flex: 6, flex: 6,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (context) => const GoogleMapScreen()),
builder: (context) => const GoogleMapScreen(),
),
); );
}, },
child: ClipRRect( child: Container(
borderRadius: const BorderRadius.only( margin: const EdgeInsets.all(8),
topRight: Radius.circular(24), child: ClipRRect(
bottomRight: Radius.circular(24), borderRadius: BorderRadius.circular(22),
), child: Stack(
child: Stack( children: [
children: [ Container(color: const Color(0xFF2C2C2E)),
Container( CustomPaint(
color: const Color(0xFF3A3A3C), size: Size.infinite,
), // Fundo do mapa. painter: MapPainter(),
CustomPaint( ),
size: Size.infinite, // Overlay estético indicando interatividade.
painter: Container(
MapPainter(), // Desenha as linhas e o marcador. decoration: BoxDecoration(
), gradient: LinearGradient(
], begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.4),
],
),
),
),
Positioned(
bottom: 12,
right: 12,
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: AppColors.background.withOpacity(0.8),
shape: BoxShape.circle,
),
child: const Icon(Icons.fullscreen_rounded, color: Colors.white, size: 20),
),
),
const Positioned(
top: 12,
left: 12,
child: Text(
"MAPA",
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w900,
letterSpacing: 1,
),
),
),
],
),
), ),
), ),
), ),
@@ -211,104 +263,55 @@ class _RunningScreenState extends State<RunningScreen>
), ),
), ),
// 4. Barra de progresso linear (centralizada acima dos botões inferiores). // 4. Barra de progresso linear centralizada.
Positioned( Positioned(
bottom: 160, bottom: 170,
left: left: 50,
40, // Espaçamento igual na esquerda e direita para centralizar. right: 50,
right: 40,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: progress, value: progress,
minHeight: 12, minHeight: 8,
backgroundColor: AppColors.backgroundGrey.withOpacity(0.3), backgroundColor: Colors.white.withOpacity(0.1),
valueColor: const AlwaysStoppedAnimation<Color>( valueColor: const AlwaysStoppedAnimation<Color>(AppColors.white),
AppColors.white,
),
), ),
), ),
), ),
// 5. Botões de menu inferiores. // 5. Menu de navegação inferior.
Positioned( Positioned(
bottom: 60, bottom: 50,
left: 0, left: 0,
right: 0, right: 0,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildMenuButton(Icons.settings, 'Configurações clicado!'), _buildMenuButton(Icons.settings_outlined, 'Configurações'),
_buildMenuButton(Icons.group_outlined, 'Grupos clicado!'), _buildMenuButton(Icons.group_outlined, 'Grupos'),
_buildMenuButton(Icons.access_time, 'Histórico clicado!'), _buildMenuButton(Icons.history_rounded, 'Histórico'),
_buildMenuButton( _buildMenuButton(Icons.notifications_none_rounded, 'Notificações', showBadge: true),
Icons.notifications_none, _buildMenuButton(Icons.person_outline_rounded, 'Perfil', isAvatar: true),
'Notificações clicado!',
showBadge: true,
),
_buildMenuButton(
Icons.person,
'Perfil clicado!',
isAvatar: true,
),
], ],
), ),
), ),
// 6. Botão de Bluetooth no canto superior direito. // 6. Ação rápida de Bluetooth.
Positioned( Positioned(
top: 60, top: 60,
right: 30, right: 25,
child: GestureDetector( child: _buildSmallActionButton(Icons.bluetooth, Colors.red),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BluetoothScreen(),
),
);
},
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. /// Item de estatística com design refinado.
Widget _buildStatItem(IconData icon, String value, String label) { Widget _buildStatItem(IconData icon, String value, String label) {
return Row( return Row(
children: [ children: [
Icon(icon, color: Colors.white70, size: 24), Icon(icon, color: AppColors.coral.withOpacity(0.8), size: 22),
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -318,13 +321,18 @@ class _RunningScreenState extends State<RunningScreen>
value, value,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18, fontSize: 19,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w900,
), ),
), ),
Text( Text(
label, label,
style: const TextStyle(color: Colors.white60, fontSize: 10), style: TextStyle(
color: Colors.white.withOpacity(0.4),
fontSize: 9,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
), ),
], ],
), ),
@@ -332,55 +340,53 @@ class _RunningScreenState extends State<RunningScreen>
); );
} }
/// Constrói um botão de menu clicável que exibe um SnackBar. /// Botões do menu com melhorias visuais.
Widget _buildMenuButton( Widget _buildMenuButton(IconData icon, String message, {bool showBadge = false, bool isAvatar = false}) {
IconData icon,
String message, {
bool showBadge = false,
bool isAvatar = false,
}) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(message), content: Text(message),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 1), duration: const Duration(seconds: 1),
), ),
); );
}, },
child: Stack( child: Stack(
clipBehavior: Clip.none,
children: [ children: [
// Exibe um avatar circular ou um ícone padrão.
isAvatar isAvatar
? CircleAvatar( ? Container(
radius: 20, padding: const EdgeInsets.all(3),
backgroundColor: Colors.orange, decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: AppColors.coral, width: 2),
),
child: const CircleAvatar( child: const CircleAvatar(
radius: 18, radius: 18,
backgroundImage: NetworkImage( backgroundImage: NetworkImage('https://i.pravatar.cc/150?u=1'),
'https://i.pravatar.cc/150?u=1',
),
), ),
) )
: Container( : Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(12),
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColors.backgroundGrey, color: AppColors.backgroundGrey,
shape: BoxShape.circle, borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.white.withOpacity(0.05)),
), ),
child: Icon(icon, color: AppColors.white, size: 24), child: Icon(icon, color: Colors.white.withOpacity(0.9), size: 24),
), ),
// Exibe um pontinho vermelho de notificação se showBadge for true.
if (showBadge) if (showBadge)
Positioned( Positioned(
left: 0, right: -2,
bottom: 0, top: -2,
child: Container( child: Container(
width: 8, width: 10,
height: 8, height: 10,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: Colors.red, color: AppColors.coral,
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: AppColors.background, width: 2),
), ),
), ),
), ),
@@ -388,68 +394,84 @@ class _RunningScreenState extends State<RunningScreen>
), ),
); );
} }
/// Botão de ação rápida (Bluetooth).
Widget _buildSmallActionButton(IconData icon, Color badgeColor) {
return GestureDetector(
onTap: () {
// Navegando para a nova tela de conexão Bluetooth
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BluetoothConnectionScreen()),
);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
shape: BoxShape.circle,
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: Stack(
children: [
Icon(icon, color: Colors.white, size: 20),
Positioned(
right: 0,
top: 0,
child: Container(
width: 7,
height: 7,
decoration: BoxDecoration(
color: badgeColor,
shape: BoxShape.circle,
border: Border.all(color: AppColors.backgroundGrey, width: 1.5),
),
),
),
],
),
),
);
}
} }
/// Pintor customizado para desenhar o traçado do mapa simulado. /// Pintor customizado para o mapa miniatura estético.
class MapPainter extends CustomPainter { class MapPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final paint = Paint() final paintPath = Paint()
..color = Colors.white38 ..color = AppColors.coral.withOpacity(0.5)
..strokeWidth = 3 ..strokeWidth = 3
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round; ..strokeCap = StrokeCap.round;
// Desenha a linha sinuosa do percurso.
final path = Path(); final path = Path();
path.moveTo(size.width * 0.1, size.height * 0.8); path.moveTo(size.width * 0.1, size.height * 0.8);
path.quadraticBezierTo( path.quadraticBezierTo(size.width * 0.3, size.height * 0.9, size.width * 0.5, size.height * 0.5);
size.width * 0.3, path.quadraticBezierTo(size.width * 0.7, size.height * 0.1, size.width * 0.9, size.height * 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 paintRoad = Paint()
final roadPaint = Paint() ..color = Colors.white.withOpacity(0.1)
..color = Colors.white ..strokeWidth = 10
..strokeWidth = 8
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final roadPath = Path(); final road = Path();
roadPath.moveTo(size.width * 0.6, size.height * 1.1); road.moveTo(0, size.height * 0.5);
roadPath.quadraticBezierTo( road.lineTo(size.width, size.height * 0.6);
size.width * 0.7,
size.height * 0.8,
size.width * 1.1,
size.height * 0.7,
);
canvas.drawPath(path, paint); canvas.drawPath(road, paintRoad);
canvas.drawPath(roadPath, roadPaint); canvas.drawPath(path, paintPath);
// Desenha o marcador circular (o pino no mapa). final markerPaint = Paint()..color = AppColors.coral;
final markerPaint = Paint()..color = const Color(0xFFFF6B6B); canvas.drawCircle(Offset(size.width * 0.5, size.height * 0.5), 5, markerPaint);
final markerPos = Offset(size.width * 0.4, size.height * 0.4); canvas.drawCircle(Offset(size.width * 0.5, size.height * 0.5), 8, Paint()..color = AppColors.coral.withOpacity(0.3));
canvas.drawCircle(markerPos, 6, markerPaint);
// Desenha o centro branco do marcador.
final innerPaint = Paint()..color = Colors.white;
canvas.drawCircle(markerPos, 2, innerPaint);
} }
@override @override
bool shouldRepaint(CustomPainter oldDelegate) => false; bool shouldRepaint(CustomPainter oldDelegate) => false;
} }
/// Pintor customizado para desenhar o arco de progresso circular. /// Pintor customizado para o arco de progresso circular.
class CircularProgressPainter extends CustomPainter { class CircularProgressPainter extends CustomPainter {
final double progress; final double progress;
final double strokeWidth; final double strokeWidth;
@@ -468,31 +490,21 @@ class CircularProgressPainter extends CustomPainter {
final center = Offset(size.width / 2, size.height / 2); final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2; final radius = (size.width - strokeWidth) / 2;
// Desenha o círculo de fundo (cinza transparente). canvas.drawCircle(center, radius, Paint()
final backgroundPaint = Paint()
..color = backgroundColor ..color = backgroundColor
..strokeWidth = strokeWidth ..strokeWidth = strokeWidth
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke);
canvas.drawCircle(center, radius, backgroundPaint);
// Desenha o arco de progresso (branco).
final progressPaint = Paint() final progressPaint = Paint()
..color = progressColor ..color = progressColor
..strokeWidth = strokeWidth ..strokeWidth = strokeWidth
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round; ..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( canvas.drawArc(
Rect.fromCircle(center: center, radius: radius), Rect.fromCircle(center: center, radius: radius),
startAngle, -1.5708, // -90 graus em radianos
sweepAngle, 6.2831 * progress, // 360 graus em radianos * progresso
false, false,
progressPaint, progressPaint,
); );

View File

@@ -0,0 +1,373 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import '../constants/app_colors.dart';
class BluetoothConnectionScreen extends StatefulWidget {
const BluetoothConnectionScreen({super.key});
@override
State<BluetoothConnectionScreen> createState() => _BluetoothConnectionScreenState();
}
class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
List<ScanResult> _scanResults = [];
bool _isScanning = false;
BluetoothDevice? _connectedDevice; // Track connected device
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
late StreamSubscription<bool> _isScanningSubscription;
@override
void initState() {
super.initState();
_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
if (mounted) {
setState(() {
// FILTRO: Mantém apenas dispositivos que possuem um nome identificado
_scanResults = results.where((r) => r.device.platformName.isNotEmpty).toList();
});
}
});
_isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
if (mounted) {
setState(() {
_isScanning = state;
});
}
});
}
@override
void dispose() {
_scanResultsSubscription.cancel();
_isScanningSubscription.cancel();
super.dispose();
}
Future<void> _requestPermissionsAndStartScan() async {
if (Platform.isAndroid) {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
if (statuses[Permission.bluetoothScan]!.isGranted &&
statuses[Permission.bluetoothConnect]!.isGranted) {
_startScan();
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Permissões de Bluetooth negadas.'),
behavior: SnackBarBehavior.floating,
),
);
}
}
} else {
_startScan();
}
}
Future<void> _startScan() async {
try {
if (await FlutterBluePlus.adapterState.first != BluetoothAdapterState.on) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Ligue o Bluetooth para buscar dispositivos.'),
behavior: SnackBarBehavior.floating,
),
);
}
return;
}
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erro ao iniciar scan: $e'),
behavior: SnackBarBehavior.floating,
),
);
}
}
}
Future<void> _stopScan() async {
try {
await FlutterBluePlus.stopScan();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erro ao parar scan: $e'),
behavior: SnackBarBehavior.floating,
),
);
}
}
}
Future<void> _connectToDevice(BluetoothDevice device) async {
try {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Conectando a ${device.platformName}...'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
await device.connect();
if (mounted) {
setState(() {
_connectedDevice = device;
_isScanning = false;
});
FlutterBluePlus.stopScan();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Conectado com sucesso!'),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erro ao conectar: $e'),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
),
);
}
}
}
Future<void> _disconnectDevice() async {
if (_connectedDevice != null) {
await _connectedDevice!.disconnect();
setState(() {
_connectedDevice = null;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
title: const Text(
'DISPOSITIVOS',
style: TextStyle(
fontWeight: FontWeight.w900,
letterSpacing: 2,
color: Colors.white,
),
),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white, size: 20),
onPressed: () => Navigator.pop(context),
),
),
body: Stack(
children: [
Column(
children: [
const SizedBox(height: 20),
// Header Card
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: AppColors.backgroundGrey,
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Colors.white.withOpacity(0.05)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_connectedDevice != null
? 'STATUS: CONECTADO'
: (_isScanning ? 'STATUS: BUSCANDO...' : 'STATUS: PRONTO'),
style: TextStyle(
color: _connectedDevice != null ? Colors.greenAccent : (_isScanning ? AppColors.coral : Colors.white54),
fontSize: 10,
fontWeight: FontWeight.w900,
letterSpacing: 1,
),
),
const SizedBox(height: 8),
Text(
_connectedDevice != null
? '1 Dispositivo'
: '${_scanResults.length} Encontrados',
style: const TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.w900,
),
),
],
),
if (_connectedDevice == null)
GestureDetector(
onTap: _isScanning ? _stopScan : _requestPermissionsAndStartScan,
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: _isScanning ? Colors.red.withOpacity(0.1) : AppColors.coral.withOpacity(0.1),
shape: BoxShape.circle,
border: Border.all(
color: _isScanning ? Colors.red.withOpacity(0.5) : AppColors.coral.withOpacity(0.5),
width: 2,
),
),
child: Icon(
_isScanning ? Icons.stop_rounded : Icons.search_rounded,
color: _isScanning ? Colors.red : AppColors.coral,
size: 28,
),
),
)
else
GestureDetector(
onTap: _disconnectDevice,
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
border: Border.all(
color: Colors.red.withOpacity(0.5),
width: 2,
),
),
child: const Icon(
Icons.close_rounded,
color: Colors.red,
size: 28,
),
),
),
],
),
),
),
const SizedBox(height: 30),
// Device List
Expanded(
child: ListView.builder(
itemCount: _connectedDevice != null ? 1 : _scanResults.length,
padding: const EdgeInsets.symmetric(horizontal: 25),
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
final BluetoothDevice device = _connectedDevice ?? _scanResults[index].device;
final name = device.platformName;
return Padding(
padding: const EdgeInsets.only(bottom: 18),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.backgroundGrey.withOpacity(0.4),
borderRadius: BorderRadius.circular(25),
border: Border.all(color: Colors.white.withOpacity(0.03)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.background.withOpacity(0.8),
borderRadius: BorderRadius.circular(15),
),
child: Icon(
Icons.bluetooth_audio_rounded,
color: _connectedDevice != null ? Colors.greenAccent : AppColors.coral.withOpacity(0.8),
size: 24
),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w900
),
),
const SizedBox(height: 4),
Text(
device.remoteId.toString(),
style: const TextStyle(
color: Colors.white38,
fontSize: 11,
fontWeight: FontWeight.bold
),
),
],
),
),
if (_connectedDevice == null)
GestureDetector(
onTap: () => _connectToDevice(device),
child: const Icon(Icons.add_link_rounded, color: Colors.white24),
)
else
const Icon(Icons.check_circle_rounded, color: Colors.greenAccent),
],
),
),
);
},
),
),
],
),
if (_isScanning)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: LinearProgressIndicator(
backgroundColor: Colors.transparent,
valueColor: const AlwaysStoppedAnimation<Color>(AppColors.coral),
minHeight: 2,
),
),
],
),
);
}
}

View File

@@ -1,5 +1,11 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart';
import '../constants/app_colors.dart';
class GoogleMapScreen extends StatefulWidget { class GoogleMapScreen extends StatefulWidget {
const GoogleMapScreen({super.key}); const GoogleMapScreen({super.key});
@@ -9,27 +15,248 @@ class GoogleMapScreen extends StatefulWidget {
} }
class _GoogleMapScreenState extends State<GoogleMapScreen> { class _GoogleMapScreenState extends State<GoogleMapScreen> {
late GoogleMapController mapController; GoogleMapController? _mapController;
StreamSubscription<Position>? _positionStreamSubscription;
Timer? _simulationTimer;
final LatLng _center = const LatLng(38.7223, -9.1393); // Lisbon coordinates // Controle de frequência de atualização para evitar sobrecarga e crashes
DateTime? _lastUpdate;
void _onMapCreated(GoogleMapController controller) { final List<LatLng> _routePoints = [];
mapController = controller; final Set<Polyline> _polylines = {};
final Set<Marker> _markers = {};
LatLng? _plannedStart;
LatLng? _plannedEnd;
bool _isPlanningMode = false;
double _currentSpeed = 0.0;
double _totalDistance = 0.0;
LatLng _currentPosition = const LatLng(38.7223, -9.1393);
bool _isLoading = true;
bool _isSimulating = false;
BitmapDescriptor? _startIcon;
BitmapDescriptor? _arrowIcon;
BitmapDescriptor? _finishIcon;
@override
void initState() {
super.initState();
_setupIconsAndTracking();
}
Future<void> _setupIconsAndTracking() async {
// Marcadores premium: tamanho ideal para visibilidade e estética
_startIcon = await _createPremiumMarker(Colors.greenAccent, Icons.play_arrow_rounded, 85);
_finishIcon = await _createPremiumMarker(AppColors.coral, Icons.flag_rounded, 85);
_arrowIcon = await _createArrowMarker(Colors.black, Colors.white, 95);
await _initTracking();
}
Future<BitmapDescriptor> _createPremiumMarker(Color color, IconData icon, double size) async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder);
final Paint shadowPaint = Paint()..color = Colors.black.withOpacity(0.4)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6);
canvas.drawCircle(Offset(size / 2, size / 2 + 3), size / 2, shadowPaint);
canvas.drawCircle(Offset(size / 2, size / 2), size / 2, Paint()..color = Colors.white);
canvas.drawCircle(Offset(size / 2, size / 2), size / 2 - 5, Paint()..color = color);
TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
textPainter.text = TextSpan(text: String.fromCharCode(icon.codePoint), style: TextStyle(fontSize: size * 0.6, fontFamily: icon.fontFamily, color: Colors.white, fontWeight: FontWeight.bold));
textPainter.layout();
textPainter.paint(canvas, Offset((size - textPainter.width) / 2, (size - textPainter.height) / 2));
final ui.Image image = await recorder.endRecording().toImage(size.toInt(), size.toInt() + 6);
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(byteData!.buffer.asUint8List());
}
Future<BitmapDescriptor> _createArrowMarker(Color color, Color borderColor, double size) async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder);
final Path path = Path();
path.moveTo(size / 2, 0);
path.lineTo(size * 0.9, size);
path.lineTo(size / 2, size * 0.7);
path.lineTo(size * 0.1, size);
path.close();
canvas.drawPath(path.shift(const Offset(0, 4)), Paint()..color = Colors.black38..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4));
canvas.drawPath(path, Paint()..color = color);
canvas.drawPath(path, Paint()..color = borderColor..style = ui.PaintingStyle.stroke..strokeWidth = 7);
final ui.Image image = await recorder.endRecording().toImage(size.toInt(), size.toInt() + 6);
final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(byteData!.buffer.asUint8List());
}
@override
void dispose() {
_positionStreamSubscription?.cancel();
_simulationTimer?.cancel();
_mapController?.dispose();
super.dispose();
}
Future<void> _initTracking() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return;
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) return;
}
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.bestForNavigation);
_currentPosition = LatLng(position.latitude, position.longitude);
setState(() => _isLoading = false);
_startLocationStream();
}
void _startLocationStream() {
_positionStreamSubscription = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 0
)
).listen((Position position) {
if (!_isSimulating) {
final now = DateTime.now();
if (_lastUpdate == null || now.difference(_lastUpdate!).inMilliseconds > 500) {
_lastUpdate = now;
_updatePosition(LatLng(position.latitude, position.longitude), position.speed);
}
}
}, onError: (error) {
debugPrint("Erro no GPS: $error");
});
}
void _updatePosition(LatLng newPoint, double speed) {
if (!mounted) return;
setState(() {
if (_routePoints.isNotEmpty) {
_totalDistance += Geolocator.distanceBetween(_currentPosition.latitude, _currentPosition.longitude, newPoint.latitude, newPoint.longitude);
}
_currentSpeed = speed >= 0 ? speed : 0;
_routePoints.add(newPoint);
_currentPosition = newPoint;
_updateMarkers(); // Agora só atualiza a seta seguidora
if (_routePoints.length > 1) {
_polylines.removeWhere((p) => p.polylineId.value == 'route' || p.polylineId.value == 'route_glow');
_polylines.add(Polyline(
polylineId: const PolylineId('route_glow'),
points: List.from(_routePoints),
color: Colors.cyanAccent.withOpacity(0.3),
width: 14,
zIndex: 9,
));
_polylines.add(Polyline(
polylineId: const PolylineId('route'),
points: List.from(_routePoints),
color: Colors.white,
width: 6,
patterns: [PatternItem.dot, PatternItem.gap(15)],
jointType: JointType.round,
zIndex: 10,
));
}
});
if (_mapController != null) {
_mapController!.animateCamera(CameraUpdate.newLatLng(newPoint));
}
}
void _updateMarkers() {
// Remove APENAS o marcador da seta seguidora para evitar duplicidade com o ponto de partida fixo
_markers.removeWhere((m) => m.markerId.value == 'follower');
_markers.add(Marker(
markerId: const MarkerId('follower'),
position: _currentPosition,
rotation: _calculateRotation(_routePoints),
flat: true,
anchor: const Offset(0.5, 0.5),
icon: _arrowIcon ?? BitmapDescriptor.defaultMarker,
zIndex: 12,
));
}
double _calculateRotation(List<LatLng> points) {
if (points.length < 2) return 0;
LatLng p1 = points[points.length - 2];
LatLng p2 = points.last;
return Geolocator.bearingBetween(p1.latitude, p1.longitude, p2.latitude, p2.longitude);
}
void _onMapTap(LatLng point) {
if (!_isPlanningMode) return;
setState(() {
if (_plannedStart == null) {
_plannedStart = point;
_markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5));
} else if (_plannedEnd == null) {
_plannedEnd = point;
_markers.add(Marker(markerId: const MarkerId('planned_end'), position: point, icon: _finishIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5));
_polylines.add(Polyline(polylineId: const PolylineId('planned_route'), points: [_plannedStart!, _plannedEnd!], color: Colors.white.withOpacity(0.1), width: 2, zIndex: 1));
} else {
_plannedStart = point;
_plannedEnd = null;
_markers.removeWhere((m) => m.markerId.value.startsWith('planned'));
_polylines.removeWhere((p) => p.polylineId.value == 'planned_route');
_markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5));
}
});
}
void _toggleSimulation() {
if (_isSimulating) {
_simulationTimer?.cancel();
setState(() => _isSimulating = false);
} else {
setState(() {
_isSimulating = true;
_isPlanningMode = false;
_routePoints.clear();
_polylines.removeWhere((p) => p.polylineId.value == 'route' || p.polylineId.value == 'route_glow');
_totalDistance = 0.0;
_currentPosition = _plannedStart ?? _currentPosition;
_routePoints.add(_currentPosition);
_updateMarkers(); // Reposiciona a seta no início
});
_simulationTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
double latStep = 0.000025;
double lngStep = 0.000025;
if (_plannedEnd != null) {
double dist = Geolocator.distanceBetween(_currentPosition.latitude, _currentPosition.longitude, _plannedEnd!.latitude, _plannedEnd!.longitude);
if (dist < 2) {
_updatePosition(_plannedEnd!, 0.0);
_toggleSimulation();
return;
}
latStep = (_plannedEnd!.latitude - _currentPosition.latitude) / (max(dist / 0.8, 1));
lngStep = (_plannedEnd!.longitude - _currentPosition.longitude) / (max(dist / 0.8, 1));
}
LatLng nextPoint = LatLng(_currentPosition.latitude + latStep, _currentPosition.longitude + lngStep);
_updatePosition(nextPoint, 3.5 + Random().nextDouble() * 0.5);
});
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: AppColors.background,
title: const Text('Mapa da Corrida'), appBar: AppBar(title: Text(_isPlanningMode ? 'PLANEJAR ROTA' : 'CORRIDA VIVA', style: const TextStyle(fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 2)), centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, leading: IconButton(icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () => Navigator.pop(context)), actions: [IconButton(icon: Icon(_isPlanningMode ? Icons.check_circle_rounded : Icons.add_location_alt_rounded, color: AppColors.coral, size: 30), onPressed: () => setState(() => _isPlanningMode = !_isPlanningMode))]),
foregroundColor: Colors.white, extendBodyBehindAppBar: true,
backgroundColor: Colors.black, // or your AppColors.background body: Stack(children: [Center(child: Container(width: MediaQuery.of(context).size.width * 0.94, height: MediaQuery.of(context).size.height * 0.7, decoration: BoxDecoration(color: AppColors.backgroundGrey, borderRadius: BorderRadius.circular(55), border: Border.all(color: Colors.white.withOpacity(0.1), width: 3), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.7), blurRadius: 50, offset: const Offset(0, 30))]), child: ClipRRect(borderRadius: BorderRadius.circular(52), child: _isLoading ? const Center(child: CircularProgressIndicator(color: AppColors.coral)) : GoogleMap(initialCameraPosition: CameraPosition(target: _currentPosition, zoom: 17.5), onMapCreated: (controller) => _mapController = controller, onTap: _onMapTap, markers: _markers, polylines: _polylines, zoomControlsEnabled: false, myLocationButtonEnabled: false, compassEnabled: false, mapToolbarEnabled: false)))), Positioned(top: 115, left: 45, right: 45, child: Container(padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 25), decoration: BoxDecoration(color: AppColors.background.withOpacity(0.95), borderRadius: BorderRadius.circular(25), border: Border.all(color: Colors.white10), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 10)]), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_buildStat("RITMO", "${(_currentSpeed * 3.6).toStringAsFixed(1)}", "KM/H"), Container(width: 1, height: 35, color: Colors.white10), _buildStat("TRAJETO", (_totalDistance / 1000).toStringAsFixed(2), "KM")]))), if (_isPlanningMode) Positioned(bottom: 140, left: 60, right: 60, child: Container(padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.black.withOpacity(0.85), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.coral.withOpacity(0.5))), child: const Text("Toque para definir Início e Fim", textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold))))]),
elevation: 0, floatingActionButton: FloatingActionButton.extended(onPressed: _toggleSimulation, label: Text(_isSimulating ? "PARAR" : "INICIAR CORRIDA", style: const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.5)), icon: Icon(_isSimulating ? Icons.stop_rounded : Icons.play_arrow_rounded, size: 32), backgroundColor: _isSimulating ? AppColors.coral : Colors.white, foregroundColor: _isSimulating ? Colors.white : AppColors.background, elevation: 15),
), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(target: _center, zoom: 13.0),
),
); );
} }
Widget _buildStat(String label, String value, String unit) {
return Column(mainAxisSize: MainAxisSize.min, children: [Text(label, style: const TextStyle(color: Colors.white54, fontSize: 10, fontWeight: FontWeight.w900, letterSpacing: 1.5)), const SizedBox(height: 6), Row(crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [Text(value, style: const TextStyle(color: Colors.white, fontSize: 26, fontWeight: FontWeight.w900)), const SizedBox(width: 3), Text(unit, style: const TextStyle(color: Colors.white54, fontSize: 10, fontWeight: FontWeight.bold))])]);
}
} }

View File

@@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation import Foundation
import flutter_blue_plus_darwin import flutter_blue_plus_darwin
import geolocator_apple
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
} }

View File

@@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@@ -97,6 +105,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -184,6 +200,54 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: f4efb8d3c4cdcad2e226af9661eb1a0dd38c71a9494b22526f9da80ab79520e5
url: "https://pub.dev"
source: hosted
version: "10.1.1"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
url: "https://pub.dev"
source: hosted
version: "4.6.2"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
url: "https://pub.dev"
source: hosted
version: "2.3.13"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
url: "https://pub.dev"
source: hosted
version: "4.2.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
url: "https://pub.dev"
source: hosted
version: "0.2.5"
google_maps: google_maps:
dependency: transitive dependency: transitive
description: description:
@@ -533,6 +597,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.1" version: "0.3.1"
uuid:
dependency: transitive
description:
name: uuid
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View File

@@ -5,7 +5,7 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=3.10.7 <4.0.0' sdk: ^3.10.7
dependencies: dependencies:
flutter: flutter:
@@ -14,7 +14,8 @@ dependencies:
flutter_map: ^6.1.0 flutter_map: ^6.1.0
latlong2: ^0.9.1 latlong2: ^0.9.1
google_maps_flutter: ^2.14.2 google_maps_flutter: ^2.14.2
flutter_blue_plus: ^1.31.11 geolocator: ^10.1.0
flutter_blue_plus: ^1.31.0
permission_handler: ^11.3.1 permission_handler: ^11.3.1
dev_dependencies: dev_dependencies:

View File

@@ -6,9 +6,12 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
} }

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
geolocator_windows
permission_handler_windows permission_handler_windows
) )