import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; void main() => runApp(const LocationApp()); class LocationApp extends StatelessWidget { const LocationApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'دقت موقعیت مکانی', theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF006C67), brightness: Brightness.light, ), useMaterial3: true, ), home: const Directionality( textDirection: TextDirection.rtl, child: LocationPage(), ), ); } } class LocationPage extends StatefulWidget { const LocationPage({super.key}); @override State createState() => _LocationPageState(); } class _LocationPageState extends State { final Stopwatch _stopwatch = Stopwatch(); Timer? _timer; Position? _position; Duration _elapsed = Duration.zero; String _status = 'دریافت خودکار موقعیت مکانی در حال شروع است…'; bool _loading = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _getLocation()); } @override void dispose() { _timer?.cancel(); _stopwatch.stop(); super.dispose(); } void _startTimer() { _timer?.cancel(); _elapsed = Duration.zero; _stopwatch ..reset() ..start(); _timer = Timer.periodic(const Duration(milliseconds: 100), (_) { if (mounted) setState(() => _elapsed = _stopwatch.elapsed); }); } void _stopTimer() { _stopwatch.stop(); _timer?.cancel(); _timer = null; _elapsed = _stopwatch.elapsed; } Future _getLocation() async { if (_loading) return; _startTimer(); setState(() { _loading = true; _status = 'در حال بررسی مجوز و دریافت موقعیت مکانی…'; }); try { final serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { _showError( kIsWeb ? 'دسترسی موقعیت مکانی مرورگر یا دستگاه خاموش است.' : 'سرویس Location دستگاه خاموش است؛ آن را روشن کنید و صفحه را دوباره باز کنید.', ); return; } var permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.denied) { _showError( 'مجوز موقعیت مکانی داده نشد. مجوز را از تنظیمات مرورگر فعال و صفحه را دوباره باز کنید.', ); return; } if (permission == LocationPermission.deniedForever) { _showError( kIsWeb ? 'دسترسی Location برای این سایت مسدود شده است؛ آن را از تنظیمات سایت مرورگر فعال کنید.' : 'مجوز Location مسدود شده است؛ آن را از تنظیمات برنامه فعال کنید.', ); return; } final position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.best, timeLimit: const Duration(seconds: 30), ); _stopTimer(); debugPrint('---------- LOCATION RESULT ----------'); debugPrint('Latitude : ${position.latitude}'); debugPrint('Longitude: ${position.longitude}'); debugPrint('Accuracy : ${position.accuracy} meters'); debugPrint('Elapsed : ${_elapsed.inMilliseconds} ms'); debugPrint('Timestamp: ${position.timestamp.toIso8601String()}'); debugPrint('-------------------------------------'); if (!mounted) return; setState(() { _position = position; _status = 'موقعیت مکانی با موفقیت دریافت شد.'; }); } on TimeoutException { _showError( 'دریافت موقعیت بیش از ۳۰ ثانیه طول کشید؛ در فضای باز صفحه را دوباره بارگذاری کنید.', ); } catch (error, stackTrace) { debugPrint('Location error: $error'); debugPrintStack(stackTrace: stackTrace); _showError( kIsWeb ? 'دریافت موقعیت ممکن نشد. دسترسی Location مرورگر را بررسی کنید. خطا: $error' : 'دریافت موقعیت ممکن نشد: $error', ); } finally { _stopTimer(); if (mounted) setState(() => _loading = false); } } void _showError(String message) { if (!mounted) return; setState(() => _status = message); } String get _formattedElapsed { final seconds = _elapsed.inMilliseconds / 1000; return '${seconds.toStringAsFixed(1)} ثانیه'; } @override Widget build(BuildContext context) { final position = _position; return Scaffold( appBar: AppBar(title: const Text('موقعیت مکانی من'), centerTitle: true), body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon( position == null ? Icons.location_searching_rounded : Icons.my_location_rounded, size: 76, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 18), Text( _status, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 18), Card( margin: EdgeInsets.zero, color: Theme.of(context).colorScheme.primaryContainer, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_loading) ...[ const SizedBox.square( dimension: 18, child: CircularProgressIndicator(strokeWidth: 2), ), const SizedBox(width: 10), ] else const Icon(Icons.timer_outlined), const SizedBox(width: 8), Text( '${_loading ? 'زمان سپری‌شده' : 'زمان نهایی'}: $_formattedElapsed', style: Theme.of(context).textTheme.titleMedium, ), ], ), ), ), const SizedBox(height: 20), if (position != null) ...[ _LocationValue( icon: Icons.north_rounded, label: 'عرض جغرافیایی (Latitude)', value: position.latitude.toStringAsFixed(7), ), const SizedBox(height: 12), _LocationValue( icon: Icons.east_rounded, label: 'طول جغرافیایی (Longitude)', value: position.longitude.toStringAsFixed(7), ), const SizedBox(height: 12), _LocationValue( icon: Icons.gps_fixed_rounded, label: 'دقت تخمینی موقعیت (Accuracy)', value: '${position.accuracy.toStringAsFixed(2)} متر', ), const SizedBox(height: 12), _LocationValue( icon: Icons.schedule_rounded, label: 'زمان ثبت', value: position.timestamp.toLocal().toString(), ), const SizedBox(height: 12), Text( 'عدد Accuracy شعاع خطای تخمینی است؛ هرچه کمتر باشد، موقعیت دقیق‌تر است.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall, ), ], ], ), ), ), ), ), ); } } class _LocationValue extends StatelessWidget { const _LocationValue({ required this.icon, required this.label, required this.value, }); final IconData icon; final String label; final String value; @override Widget build(BuildContext context) { return Card( margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Icon(icon, color: Theme.of(context).colorScheme.primary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: Theme.of(context).textTheme.bodySmall), const SizedBox(height: 3), Directionality( textDirection: TextDirection.ltr, child: SelectableText( value, style: Theme.of(context).textTheme.titleLarge, ), ), ], ), ), ], ), ), ); } }