diff --git a/lib/main.dart b/lib/main.dart index 9627183..58862ea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,7 @@ +import 'dart:ui'; + import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get/get.dart'; @@ -6,8 +9,10 @@ import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart'; import 'package:terepi_seged/routes/app_pages.dart'; import 'package:terepi_seged/services/app_database.dart'; +import 'package:terepi_seged/services/app_logger.dart'; import 'package:terepi_seged/services/coord_converter_service.dart'; import 'package:terepi_seged/services/device_identity_service.dart'; +import 'package:terepi_seged/services/firebase_logger.dart'; import 'package:terepi_seged/services/gnss/gnss_device_service.dart'; import 'package:terepi_seged/services/gnss/gnss_service.dart'; import 'package:terepi_seged/services/layer_import_service.dart'; @@ -20,6 +25,17 @@ import 'package:terepi_seged/services/track_sync_service.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); + + FlutterError.onError = (details) { + FlutterError.presentError(details); + FirebaseCrashlytics.instance.recordFlutterFatalError(details); + }; + + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; + await dotenv.load(fileName: ".env"); await Supabase.initialize( @@ -29,6 +45,8 @@ Future main() async { await AppDatabase.instance.database; Get.put(ProjectService(), permanent: true); + Get.put(AppLogger(), permanent: true); + Get.put(FirebaseLogger(), permanent: true); await Get.putAsync( () => CoordConverterService().init()); Get.put(GnssDeviceService()); diff --git a/lib/pages/tracking/presentation/controllers/tracking_controller.dart b/lib/pages/tracking/presentation/controllers/tracking_controller.dart index 9ee115e..9471c44 100644 --- a/lib/pages/tracking/presentation/controllers/tracking_controller.dart +++ b/lib/pages/tracking/presentation/controllers/tracking_controller.dart @@ -1,10 +1,14 @@ import 'dart:async'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:terepi_seged/services/app_logger.dart'; +import 'package:terepi_seged/services/firebase_logger.dart'; import 'package:terepi_seged/services/project_service.dart'; import 'package:terepi_seged/services/track_sync_service.dart'; @@ -169,11 +173,18 @@ class TrackingController extends GetxController { _positionSub = _source!.positionStream.listen( _onPosition, onError: (e) { - Get.snackbar('GPS hiba', e.toString(), - backgroundColor: Colors.red, colorText: Colors.white); - stopRecording(); + AppLogger.e('positionStream', 'Stream hiba', error: e); + FirebaseLogger.e('positionStream', 'Stream hiba', error: e); + if (e is LocationServiceDisabledException || + e is PermissionDeniedException) { + Get.snackbar('GPS hiba', e.toString(), + backgroundColor: Colors.red, colorText: Colors.white); + stopRecording(); + } }, ); + AppLogger.e('track_started', 'name:$trackName'); + FirebaseLogger.event('track_started', {'name': trackName}); // Időmérő _startElapsedTimer(); @@ -268,57 +279,75 @@ class TrackingController extends GetxController { // ── Belső logika ─────────────────────────────────────────────────────────── Future _onPosition(SourcePosition pos) async { - if (isPaused.value) return; + try { + if (isPaused.value) return; + final sw = Stopwatch()..start(); - final trackId = currentTrack.value?.id; - if (trackId == null) return; + final trackId = currentTrack.value?.id; + if (trackId == null) return; - // Távolság a legutóbbi ponttól - double segmentDist = 0; - if (_lastPoint != null) { - segmentDist = haversineMeters( - _lastPoint!.latitude, - _lastPoint!.longitude, - pos.latitude, - pos.longitude, + // Távolság a legutóbbi ponttól + double segmentDist = 0; + if (_lastPoint != null) { + segmentDist = haversineMeters( + _lastPoint!.latitude, + _lastPoint!.longitude, + pos.latitude, + pos.longitude, + ); + // Szűrés: ugrásszerű változás (pl. GPS lock elvesztése) ignorálása + if (segmentDist > 100) return; + } + + _accumulatedDistance += segmentDist; + sessionDistance.value = _accumulatedDistance; + + // Sebesség km/h-ban + currentSpeedKmh.value = (pos.speed ?? 0) * 3.6; + + // Pont mentése + final point = TrackPoint( + trackId: trackId, + latitude: pos.latitude, + longitude: pos.longitude, + altitude: pos.altitude, + accuracy: pos.accuracy, + speed: pos.speed, + heading: pos.heading, + timestamp: pos.timestamp, ); - // Szűrés: ugrásszerű változás (pl. GPS lock elvesztése) ignorálása - if (segmentDist > 100) return; - } + await _db.addPoint(point, _accumulatedDistance); + sw.stop(); - _accumulatedDistance += segmentDist; - sessionDistance.value = _accumulatedDistance; + if (sw.elapsedMilliseconds > 300) { + AppLogger.w( + 'on_Position', + 'Lassú addPoint: ${sw.elapsedMilliseconds}ms, ' + 'pts: ${livePoints.length}'); + FirebaseLogger.w( + 'on_Position', + 'Lassú addPoint: ${sw.elapsedMilliseconds}ms, ' + 'pts: ${livePoints.length}'); + } - // Sebesség km/h-ban - currentSpeedKmh.value = (pos.speed ?? 0) * 3.6; + TrackSyncService.to.onNewPoint(point); - // Pont mentése - final point = TrackPoint( - trackId: trackId, - latitude: pos.latitude, - longitude: pos.longitude, - altitude: pos.altitude, - accuracy: pos.accuracy, - speed: pos.speed, - heading: pos.heading, - timestamp: pos.timestamp, - ); - await _db.addPoint(point, _accumulatedDistance); + _lastPoint = point; - TrackSyncService.to.onNewPoint(point); + // UI frissítés + livePoints.add(LatLng(pos.latitude, pos.longitude)); - _lastPoint = point; - - // UI frissítés - livePoints.add(LatLng(pos.latitude, pos.longitude)); - - // Értesítés frissítése - if (livePoints.length % 10 == 0) { - final dist = _formatDistance(_accumulatedDistance); - FlutterForegroundTask.updateService( - notificationTitle: 'Track rögzítése – $dist', - notificationText: elapsedFormatted.value, - ); + // Értesítés frissítése + if (livePoints.length % 10 == 0) { + final dist = _formatDistance(_accumulatedDistance); + FlutterForegroundTask.updateService( + notificationTitle: 'Track rögzítése – $dist', + notificationText: elapsedFormatted.value, + ); + } + } catch (e, stack) { + AppLogger.e('_onPosition', 'pont feldolgozási hiba - track folytatódik', + error: e, stack: stack); } } diff --git a/lib/services/device_identity_service.dart b/lib/services/device_identity_service.dart index bd662f3..fecaa32 100644 --- a/lib/services/device_identity_service.dart +++ b/lib/services/device_identity_service.dart @@ -14,6 +14,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:terepi_seged/services/app_logger.dart'; +import 'package:terepi_seged/services/firebase_logger.dart'; import 'package:uuid/uuid.dart'; import '../models/device_info_model.dart'; @@ -58,6 +60,11 @@ class DeviceIdentityService extends GetxService { // Háttérben regisztrálás — nem blokkolja az UI-t unawaited(_registerDevice()); _isReady = true; + + FirebaseLogger.setDeviceIdentifier(info.deviceId, deviceLabel.value); + FirebaseLogger.setKey('model', model); + FirebaseLogger.setKey('os_version', osInfo); + FirebaseLogger.setKey('app_version', appInfo); } // ── Betöltés ────────────────────────────────────────────────────── diff --git a/lib/services/firebase_logger.dart b/lib/services/firebase_logger.dart new file mode 100644 index 0000000..f4ff182 --- /dev/null +++ b/lib/services/firebase_logger.dart @@ -0,0 +1,190 @@ +// Firebase alapú log service — Crashlytics + Analytics +// +// Crashlytics: breadcrumb log, hiba rögzítés, egyedi attribútumok +// Analytics: egyedi esemény tracking (warning-ok, hibák, fontos események) +// +// Éles appban is működik — kiegészíti az AppLogger-t (ami csak debug módban fut) +// +// Használat: +// FirebaseLogger.i('startRecording', 'Track indítva: $name'); +// FirebaseLogger.w('_onPosition', 'Ugrás kiszűrve: ${dist}m'); +// FirebaseLogger.e('positionStream', 'GPS hiba', error: e, stack: s); +// FirebaseLogger.event('track_completed', {'distance_m': 3200, 'points': 142}); + +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; + +class FirebaseLogger extends GetxService { + static FirebaseLogger get to => Get.find(); + + // ── Belső referenciák ───────────────────────────────────────────── + + late final FirebaseCrashlytics _crashlytics; + late final FirebaseAnalytics _analytics; + + bool _isReady = false; + + // ── Lifecycle ───────────────────────────────────────────────────── + + @override + Future onReady() async { + super.onReady(); + try { + _crashlytics = FirebaseCrashlytics.instance; + _analytics = FirebaseAnalytics.instance; + + // Crashlytics engedélyezése — release-ben mindig, debug-ban opcionális + await _crashlytics.setCrashlyticsCollectionEnabled(!kDebugMode); + + // Eszközazonosító beállítása ha már elérhető + _setDeviceInfo(); + + _isReady = true; + _crashlytics.log('FirebaseLogger inicializálva'); + } catch (e) { + debugPrint('FirebaseLogger init hiba: $e'); + } + } + + // ── Publikus API — ugyanolyan mint AppLogger ────────────────────── + + /// Info szintű log — Crashlytics breadcrumb + static void i(String tag, String message) { + _breadcrumb('I', tag, message); + } + + /// Figyelmeztetés — breadcrumb + Analytics esemény + static void w(String tag, String message, {Object? error}) { + _breadcrumb('W', tag, message); + _logWarningEvent(tag, message, error: error); + } + + /// Hiba — Crashlytics recordError + Analytics esemény + static void e( + String tag, + String message, { + Object? error, + StackTrace? stack, + bool fatal = false, + }) { + _breadcrumb('E', tag, message); + + if (error != null) { + try { + FirebaseLogger.to._crashlytics.recordError( + error, + stack, + reason: '[$tag] $message', + fatal: fatal, + printDetails: kDebugMode, + information: ['tag: $tag', 'message: $message'], + ); + } catch (_) {} + } + + _logErrorEvent(tag, message, error: error); + } + + /// Szeparátor — jól látható elválasztó a Crashlytics logban + static void separator(String label) { + _breadcrumb('─', '─────', '─── $label ───────────────────────'); + } + + /// Egyedi Analytics esemény — tetszőleges adatokkal + static void event( + String name, [ + Map? parameters, + ]) { + try { + FirebaseLogger.to._analytics.logEvent( + name: _sanitizeEventName(name), + parameters: parameters, + ); + } catch (_) {} + } + + /// Egyedi Crashlytics attribútum beállítása + static void setKey(String key, dynamic value) { + try { + FirebaseLogger.to._crashlytics.setCustomKey(key, value); + } catch (_) {} + } + + // ── Eszközinfo beállítása ───────────────────────────────────────── + + void _setDeviceInfo() { + try { + // DeviceIdentityService ha már elérhető + if (Get.isRegistered()) { + final deviceService = Get.find(tag: 'DeviceIdentityService'); + _crashlytics.setUserIdentifier(deviceService.deviceId ?? ''); + } + } catch (_) { + // DeviceIdentityService még nem regisztrált — nem baj + } + } + + /// Crashlytics azonosító frissítése ha a DeviceIdentityService betöltött + static void setDeviceIdentifier(String deviceId, String label) { + try { + FirebaseLogger.to._crashlytics.setUserIdentifier(deviceId); + FirebaseLogger.to._crashlytics.setCustomKey('device_label', label); + } catch (_) {} + } + + // ── Belső segédek ───────────────────────────────────────────────── + + static void _breadcrumb(String level, String tag, String message) { + try { + final line = '$level [$tag] $message'; + FirebaseLogger.to._crashlytics.log(line); + } catch (_) {} + } + + static void _logWarningEvent( + String tag, + String message, { + Object? error, + }) { + try { + FirebaseLogger.to._analytics.logEvent( + name: 'app_warning', + parameters: { + 'tag': _trim(tag, 40), + 'message': _trim(message, 100), + if (error != null) 'error': _trim(error.toString(), 100), + }, + ); + } catch (_) {} + } + + static void _logErrorEvent( + String tag, + String message, { + Object? error, + }) { + try { + FirebaseLogger.to._analytics.logEvent( + name: 'app_error', + parameters: { + 'tag': _trim(tag, 40), + 'message': _trim(message, 100), + if (error != null) 'error': _trim(error.toString(), 100), + }, + ); + } catch (_) {} + } + + /// Analytics eseménynevek csak betűt, számot és _ -t tartalmazhatnak + static String _sanitizeEventName(String name) => + name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_').substring( + 0, + name.length.clamp(0, 40), + ); + + /// Analytics paraméter érték max 100 karakter + static String _trim(String s, int max) => + s.length > max ? s.substring(0, max) : s; +} diff --git a/pubspec.yaml b/pubspec.yaml index a8b9936..3dbec71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,10 +41,10 @@ dependencies: flutter_map_location_marker: ^10.1.0 path_provider: ^2.1.5 rive: ^0.13.20 - firebase_core: ^3.12.0 - firebase_auth: ^5.5.0 - cloud_firestore: ^5.6.4 - firebase_storage: ^12.4.3 + firebase_core: ^4.11.0 + firebase_auth: ^6.5.4 + cloud_firestore: ^6.6.0 + firebase_storage: ^13.4.3 firebase_crashlytics: ^5.2.4 firebase_analytics: ^12.4.3 google_sign_in: ^6.2.2