@@ -2,11 +2,13 @@ import 'dart:async';
22import 'dart:math' ;
33import 'package:flutter/material.dart' ;
44import 'package:sensors_plus/sensors_plus.dart' ;
5- import 'package:flutter/foundation.dart' ;
5+ import 'package:geolocator/geolocator.dart' ;
6+ import 'package:intl/intl.dart' ;
67import 'package:pslab/others/logger_service.dart' ;
78
89import '../l10n/app_localizations.dart' ;
910import 'locator.dart' ;
11+ import 'compass_config_provider.dart' ;
1012
1113class CompassProvider extends ChangeNotifier {
1214 AppLocalizations appLocalizations = getIt.get <AppLocalizations >();
@@ -21,12 +23,69 @@ class CompassProvider extends ChangeNotifier {
2123 int _direction = 0 ;
2224 double _smoothedHeading = 0.0 ;
2325
26+ bool _isRecording = false ;
27+ List <List <dynamic >> _recordedData = [];
28+ Timer ? _recordTimer;
29+
30+ bool _isPlayingBack = false ;
31+ List <List <dynamic >>? _playbackData;
32+ int _playbackIndex = 0 ;
33+ Timer ? _playbackTimer;
34+ bool _isPlaybackPaused = false ;
35+ Function ? onPlaybackEnd;
36+ double _playbackDegree = 0.0 ;
37+
38+ CompassConfigProvider ? _configProvider;
39+ Position ? currentPosition;
40+ StreamSubscription ? _locationStream;
41+
2442 MagnetometerEvent get magnetometerEvent => _magnetometerEvent;
2543 AccelerometerEvent get accelerometerEvent => _accelerometerEvent;
2644 String get selectedAxis => _selectedAxis;
2745 double get currentDegree => _currentDegree;
2846 int get direction => _direction;
2947 double get smoothedHeading => _smoothedHeading;
48+ bool get isRecording => _isRecording;
49+ bool get isPlayingBack => _isPlayingBack;
50+ bool get isPlaybackPaused => _isPlaybackPaused;
51+
52+ void setConfigProvider (CompassConfigProvider configProvider) {
53+ _configProvider = configProvider;
54+ }
55+
56+ Future <void > _startGeoLocationUpdates () async {
57+ bool serviceEnabled;
58+ LocationPermission permission;
59+
60+ serviceEnabled = await Geolocator .isLocationServiceEnabled ();
61+ if (! serviceEnabled) {
62+ logger.w ('Location services are disabled.' );
63+ return ;
64+ }
65+
66+ permission = await Geolocator .checkPermission ();
67+ if (permission == LocationPermission .denied) {
68+ permission = await Geolocator .requestPermission ();
69+ if (permission == LocationPermission .denied) {
70+ logger.w ('Location permissions are denied' );
71+ return ;
72+ }
73+ }
74+
75+ if (permission == LocationPermission .deniedForever) {
76+ logger.w (
77+ 'Location permissions are permanently denied, we cannot request permissions.' );
78+ return ;
79+ }
80+
81+ _locationStream = Geolocator .getPositionStream (
82+ locationSettings: const LocationSettings (
83+ accuracy: LocationAccuracy .high,
84+ ),
85+ ).listen ((Position position) {
86+ currentPosition = position;
87+ });
88+ }
3089
3190 void initializeSensors () {
3291 _magnetometerSubscription = magnetometerEventStream ().listen (
@@ -57,11 +116,173 @@ class CompassProvider extends ChangeNotifier {
57116 void disposeSensors () {
58117 _magnetometerSubscription? .cancel ();
59118 _accelerometerSubscription? .cancel ();
119+ _playbackTimer? .cancel ();
120+ }
121+
122+ Future <void > startPlayback (List <List <dynamic >> data) async {
123+ if (data.length <= 1 ) return ;
124+ _isPlayingBack = true ;
125+ _isPlaybackPaused = false ;
126+ _playbackTimer? .cancel ();
127+ _playbackData = data;
128+ _playbackIndex = 1 ;
129+ disposeSensors ();
130+ _startPlaybackTimer ();
131+ notifyListeners ();
132+ }
133+
134+ void _startPlaybackTimer () {
135+ if (_playbackIndex >= _playbackData! .length) {
136+ stopPlayback ();
137+ return ;
138+ }
139+
140+ final currentRow = _playbackData! [_playbackIndex];
141+ if (currentRow.length > 5 ) {
142+ final bx = double .tryParse (currentRow[2 ].toString ()) ?? 0.0 ;
143+ final by = double .tryParse (currentRow[3 ].toString ()) ?? 0.0 ;
144+ final bz = double .tryParse (currentRow[4 ].toString ()) ?? 0.0 ;
145+ final degree = double .tryParse (currentRow[5 ].toString ()) ?? 0.0 ;
146+
147+ _magnetometerEvent = MagnetometerEvent (bx, by, bz, DateTime .now ());
148+ _playbackDegree = degree;
149+
150+ if (_selectedAxis == 'Z' ) {
151+ _currentDegree = - ((_playbackDegree - 90 ) * pi / 180 );
152+ } else {
153+ _currentDegree = - (_playbackDegree * pi / 180 );
154+ }
155+
156+ _playbackIndex++ ;
157+ notifyListeners ();
158+ } else if (currentRow.length > 4 ) {
159+ final bx = double .tryParse (currentRow[2 ].toString ()) ?? 0.0 ;
160+ final by = double .tryParse (currentRow[3 ].toString ()) ?? 0.0 ;
161+ final bz = double .tryParse (currentRow[4 ].toString ()) ?? 0.0 ;
162+
163+ _magnetometerEvent = MagnetometerEvent (bx, by, bz, DateTime .now ());
164+ _accelerometerEvent = AccelerometerEvent (0 , 0 , 9.8 , DateTime .now ());
165+
166+ _updateCompassDirection ();
167+ _playbackIndex++ ;
168+ notifyListeners ();
169+ } else {
170+ logger.e (
171+ 'Skipping playback row at index $_playbackIndex due to insufficient columns' );
172+ _playbackIndex++ ;
173+ notifyListeners ();
174+ }
175+
176+ Duration interval = const Duration (seconds: 1 );
177+
178+ if (_playbackIndex < _playbackData! .length && _playbackIndex > 1 ) {
179+ try {
180+ final currentTimestamp =
181+ int .tryParse (_playbackData! [_playbackIndex - 1 ][0 ].toString ());
182+ final nextTimestamp =
183+ int .tryParse (_playbackData! [_playbackIndex][0 ].toString ());
184+
185+ if (currentTimestamp != null && nextTimestamp != null ) {
186+ final timeDiff = nextTimestamp - currentTimestamp;
187+ interval = Duration (milliseconds: timeDiff);
188+ if (interval.inMilliseconds < 100 ) {
189+ interval = const Duration (milliseconds: 100 );
190+ } else if (interval.inMilliseconds > 10000 ) {
191+ interval = const Duration (seconds: 10 );
192+ }
193+ }
194+ } catch (e) {
195+ interval = const Duration (seconds: 1 );
196+ }
197+ }
198+
199+ _playbackTimer = Timer (interval, () {
200+ if (_isPlayingBack && ! _isPlaybackPaused) {
201+ _startPlaybackTimer ();
202+ }
203+ });
204+ }
205+
206+ Future <void > stopPlayback () async {
207+ _isPlayingBack = false ;
208+ _isPlaybackPaused = false ;
209+ _playbackData = null ;
210+ _playbackIndex = 0 ;
211+ _playbackDegree = 0.0 ;
212+ _magnetometerEvent = MagnetometerEvent (0 , 0 , 0 , DateTime .now ());
213+ _accelerometerEvent = AccelerometerEvent (0 , 0 , 0 , DateTime .now ());
214+ notifyListeners ();
215+ onPlaybackEnd? .call ();
216+ }
217+
218+ void pausePlayback () {
219+ if (_isPlayingBack) {
220+ _isPlaybackPaused = true ;
221+ _playbackTimer? .cancel ();
222+ notifyListeners ();
223+ }
224+ }
225+
226+ void resumePlayback () {
227+ if (_isPlayingBack && _isPlaybackPaused) {
228+ _isPlaybackPaused = false ;
229+ _startPlaybackTimer ();
230+ notifyListeners ();
231+ }
232+ }
233+
234+ Future <void > startRecording () async {
235+ if (_configProvider? .config.includeLocationData == true ) {
236+ await _startGeoLocationUpdates ();
237+ }
238+ _isRecording = true ;
239+ _recordedData = [
240+ [
241+ 'Timestamp' ,
242+ 'DateTime' ,
243+ 'Bx' ,
244+ 'By' ,
245+ 'Bz' ,
246+ 'Degree' ,
247+ 'Latitude' ,
248+ 'Longitude'
249+ ]
250+ ];
251+
252+ _recordTimer = Timer .periodic (const Duration (seconds: 1 ), (timer) {
253+ final now = DateTime .now ();
254+ final dateFormat = DateFormat ('yyyy-MM-dd HH:mm:ss.SSS' );
255+ _recordedData.add ([
256+ now.millisecondsSinceEpoch.toString (),
257+ dateFormat.format (now),
258+ _magnetometerEvent.x.toStringAsFixed (2 ),
259+ _magnetometerEvent.y.toStringAsFixed (2 ),
260+ _magnetometerEvent.z.toStringAsFixed (2 ),
261+ getDegreeForAxis (_selectedAxis).toStringAsFixed (1 ),
262+ _configProvider? .config.includeLocationData == true
263+ ? currentPosition? .latitude.toString () ?? 0
264+ : 0 ,
265+ _configProvider? .config.includeLocationData == true
266+ ? currentPosition? .longitude.toString () ?? 0
267+ : 0
268+ ]);
269+ });
270+ notifyListeners ();
271+ }
272+
273+ List <List <dynamic >> stopRecording () {
274+ _isRecording = false ;
275+ _recordTimer? .cancel ();
276+ _locationStream? .cancel ();
277+ notifyListeners ();
278+ return _recordedData;
60279 }
61280
62281 @override
63282 void dispose () {
64283 disposeSensors ();
284+ _recordTimer? .cancel ();
285+ _locationStream? .cancel ();
65286 super .dispose ();
66287 }
67288
@@ -135,6 +356,10 @@ class CompassProvider extends ChangeNotifier {
135356 }
136357
137358 double getDegreeForAxis (String axis) {
359+ if (_isPlayingBack) {
360+ return _playbackDegree;
361+ }
362+
138363 double radians = _getRadiansForAxis (axis);
139364 double degree = radians * (180 / pi);
140365
0 commit comments