mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
Improvements to suspected locations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"1.3.1": {
|
||||
"content": "• UX: Network status indicator is now always enabled by default\n• UX: Direction control buttons now use configurable dimensions\n• INTERNAL: Cleaned up advanced settings - removed redundant network status toggle"
|
||||
"content": "• UX: Network status indicator is now always enabled by default\n• UX: Direction control buttons now use configurable dimensions\n• UX: Fixed iOS keyboard missing 'Done' button on integer input fields\n• UX: Direction control buttons now always visible but greyed out when not needed\n• UX: Fixed multi-direction nodes showing all directions in upload queue\n• UX: Improved suspected locations loading indicator - removed popup, fixed stuck spinner\n• INTERNAL: Cleaned up advanced settings - removed redundant network status toggle"
|
||||
},
|
||||
"1.2.8": {
|
||||
"content": "• UX: Profile selection is now a required step to prevent accidental submission of default profile.\n• NEW: Note in welcome message about not submitting data you cannot vouch for personally (no street view etc)\n• NEW: Added default operator profiles for the most common private operators nationwide (Lowe's, Home Depot, et al)\n• NEW: Support for cardinal directions in OSM data, multiple directions on a node."
|
||||
|
||||
@@ -485,10 +485,8 @@ class AppState extends ChangeNotifier {
|
||||
await _suspectedLocationState.setEnabled(enabled);
|
||||
}
|
||||
|
||||
Future<bool> refreshSuspectedLocations({
|
||||
void Function(String message, double? progress)? onProgress,
|
||||
}) async {
|
||||
return await _suspectedLocationState.refreshData(onProgress: onProgress);
|
||||
Future<bool> refreshSuspectedLocations() async {
|
||||
return await _suspectedLocationState.refreshData();
|
||||
}
|
||||
|
||||
void selectSuspectedLocation(SuspectedLocation location) {
|
||||
|
||||
@@ -70,7 +70,7 @@ class _MaxNodesSectionState extends State<MaxNodesSection> {
|
||||
width: 80,
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.number,
|
||||
keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true),
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
|
||||
@@ -181,7 +181,7 @@ class _ProximityAlertsSectionState extends State<ProximityAlertsSection> {
|
||||
width: 80,
|
||||
child: TextField(
|
||||
controller: _distanceController,
|
||||
keyboardType: TextInputType.number,
|
||||
keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true),
|
||||
textInputAction: TextInputAction.done,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../app_state.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
import '../../../widgets/suspected_location_progress_dialog.dart';
|
||||
|
||||
class SuspectedLocationsSection extends StatelessWidget {
|
||||
const SuspectedLocationsSection({super.key});
|
||||
@@ -39,31 +38,19 @@ class SuspectedLocationsSection extends StatelessWidget {
|
||||
Future<void> handleRefresh() async {
|
||||
if (!context.mounted) return;
|
||||
|
||||
// Show simple progress dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (progressContext) => SuspectedLocationProgressDialog(
|
||||
title: locService.t('suspectedLocations.updating'),
|
||||
message: locService.t('suspectedLocations.downloadingAndProcessing'),
|
||||
),
|
||||
);
|
||||
|
||||
// Start the refresh
|
||||
// Use the inline loading indicator by calling refreshSuspectedLocations
|
||||
// The loading state will be managed by suspected location state
|
||||
final success = await appState.refreshSuspectedLocations();
|
||||
|
||||
// Close progress dialog
|
||||
// Show result snackbar
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// Show result snackbar
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(success
|
||||
? locService.t('suspectedLocations.updateSuccess')
|
||||
: locService.t('suspectedLocations.updateFailed')),
|
||||
),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(success
|
||||
? locService.t('suspectedLocations.updateSuccess')
|
||||
: locService.t('suspectedLocations.updateFailed')),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +126,7 @@ class SuspectedLocationsSection extends StatelessWidget {
|
||||
width: 80,
|
||||
child: TextFormField(
|
||||
initialValue: appState.suspectedLocationMinDistance.toString(),
|
||||
keyboardType: TextInputType.number,
|
||||
keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true),
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
|
||||
@@ -127,9 +127,8 @@ class SuspectedLocationCache extends ChangeNotifier {
|
||||
/// Process raw CSV data and save to storage (calculates centroids once)
|
||||
Future<void> processAndSave(
|
||||
List<Map<String, dynamic>> rawData,
|
||||
DateTime fetchTime, {
|
||||
void Function(String message, double? progress)? onProgress,
|
||||
}) async {
|
||||
DateTime fetchTime,
|
||||
) async {
|
||||
try {
|
||||
debugPrint('[SuspectedLocationCache] Processing ${rawData.length} raw entries...');
|
||||
|
||||
@@ -141,10 +140,9 @@ class SuspectedLocationCache extends ChangeNotifier {
|
||||
for (int i = 0; i < rawData.length; i++) {
|
||||
final rowData = rawData[i];
|
||||
|
||||
// Report progress every 1000 entries
|
||||
// Log progress every 1000 entries for debugging
|
||||
if (i % 1000 == 0) {
|
||||
final progress = i / rawData.length;
|
||||
onProgress?.call('Calculating coordinates: ${i + 1}/${rawData.length}', progress);
|
||||
debugPrint('[SuspectedLocationCache] Processed ${i + 1}/${rawData.length} entries...');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -22,7 +22,6 @@ class SuspectedLocationService {
|
||||
|
||||
final SuspectedLocationCache _cache = SuspectedLocationCache();
|
||||
bool _isEnabled = false;
|
||||
bool _isLoading = false;
|
||||
|
||||
/// Get last fetch time
|
||||
DateTime? get lastFetchTime => _cache.lastFetchTime;
|
||||
@@ -30,9 +29,6 @@ class SuspectedLocationService {
|
||||
/// Check if suspected locations are enabled
|
||||
bool get isEnabled => _isEnabled;
|
||||
|
||||
/// Check if currently loading
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
/// Initialize the service - load from storage and check if refresh needed
|
||||
Future<void> init({bool offlineMode = false}) async {
|
||||
await _loadFromStorage();
|
||||
@@ -55,22 +51,31 @@ class SuspectedLocationService {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefsKeyEnabled, enabled);
|
||||
|
||||
// If enabling for the first time and no data, fetch it in background
|
||||
if (enabled && !_cache.hasData) {
|
||||
_fetchData(); // Don't await - let it run in background so UI updates immediately
|
||||
}
|
||||
|
||||
// If disabling, clear the cache
|
||||
if (!enabled) {
|
||||
_cache.clear();
|
||||
}
|
||||
// Note: If enabling and no data, the state layer will call fetchDataIfNeeded()
|
||||
}
|
||||
|
||||
/// Manually refresh the data
|
||||
Future<bool> refreshData({
|
||||
void Function(String message, double? progress)? onProgress,
|
||||
}) async {
|
||||
return await _fetchData(onProgress: onProgress);
|
||||
/// Check if cache has any data
|
||||
bool get hasData => _cache.hasData;
|
||||
|
||||
/// Get last fetch time
|
||||
DateTime? get lastFetch => _cache.lastFetchTime;
|
||||
|
||||
/// Fetch data if needed (for enabling suspected locations when no data exists)
|
||||
Future<bool> fetchDataIfNeeded() async {
|
||||
if (!_shouldRefresh()) {
|
||||
debugPrint('[SuspectedLocationService] Data is fresh, skipping fetch');
|
||||
return true; // Already have fresh data
|
||||
}
|
||||
return await _fetchData();
|
||||
}
|
||||
|
||||
/// Force refresh the data (for manual refresh button)
|
||||
Future<bool> forceRefresh() async {
|
||||
return await _fetchData();
|
||||
}
|
||||
|
||||
/// Check if data should be refreshed
|
||||
@@ -95,14 +100,8 @@ class SuspectedLocationService {
|
||||
}
|
||||
|
||||
/// Fetch data from the CSV URL
|
||||
Future<bool> _fetchData({
|
||||
void Function(String message, double? progress)? onProgress,
|
||||
}) async {
|
||||
if (_isLoading) return false;
|
||||
|
||||
_isLoading = true;
|
||||
Future<bool> _fetchData() async {
|
||||
try {
|
||||
onProgress?.call('Downloading CSV data...', null);
|
||||
debugPrint('[SuspectedLocationService] Fetching CSV data from $kSuspectedLocationsCsvUrl');
|
||||
|
||||
final response = await http.get(
|
||||
@@ -117,8 +116,6 @@ class SuspectedLocationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
onProgress?.call('Parsing CSV data...', 0.2);
|
||||
|
||||
// Parse CSV with proper field separator and quote handling
|
||||
final csvData = await compute(_parseCSV, response.body);
|
||||
debugPrint('[SuspectedLocationService] Parsed ${csvData.length} rows from CSV');
|
||||
@@ -170,11 +167,6 @@ class SuspectedLocationService {
|
||||
validRows++;
|
||||
}
|
||||
|
||||
// Report progress every 1000 rows
|
||||
if (rowIndex % 1000 == 0) {
|
||||
final progress = 0.4 + (rowIndex / dataRows.length) * 0.4; // 40% to 80% of total
|
||||
onProgress?.call('Processing row $rowIndex...', progress);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
// Skip rows that can't be parsed
|
||||
debugPrint('[SuspectedLocationService] Error parsing row $rowIndex: $e');
|
||||
@@ -182,18 +174,12 @@ class SuspectedLocationService {
|
||||
}
|
||||
}
|
||||
|
||||
onProgress?.call('Calculating coordinates...', 0.8);
|
||||
debugPrint('[SuspectedLocationService] Parsed $validRows valid rows from ${dataRows.length} total rows');
|
||||
|
||||
final fetchTime = DateTime.now();
|
||||
|
||||
// Process raw data and save (calculates centroids once)
|
||||
await _cache.processAndSave(rawDataList, fetchTime, onProgress: (message, progress) {
|
||||
// Map cache progress to final 20% (0.8 to 1.0)
|
||||
final finalProgress = 0.8 + (progress ?? 0) * 0.2;
|
||||
onProgress?.call(message, finalProgress);
|
||||
});
|
||||
|
||||
onProgress?.call('Complete!', 1.0);
|
||||
await _cache.processAndSave(rawDataList, fetchTime);
|
||||
|
||||
debugPrint('[SuspectedLocationService] Successfully fetched and stored $validRows valid raw entries (${rawDataList.length} total)');
|
||||
return true;
|
||||
@@ -202,8 +188,6 @@ class SuspectedLocationService {
|
||||
debugPrint('[SuspectedLocationService] Error fetching data: $e');
|
||||
debugPrint('[SuspectedLocationService] Stack trace: $stackTrace');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class SuspectedLocationState extends ChangeNotifier {
|
||||
bool get isEnabled => _service.isEnabled;
|
||||
|
||||
/// Whether currently loading data
|
||||
bool get isLoading => _isLoading || _service.isLoading;
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
/// Last time data was fetched
|
||||
DateTime? get lastFetchTime => _service.lastFetchTime;
|
||||
@@ -45,18 +45,36 @@ class SuspectedLocationState extends ChangeNotifier {
|
||||
/// Enable or disable suspected locations
|
||||
Future<void> setEnabled(bool enabled) async {
|
||||
await _service.setEnabled(enabled);
|
||||
|
||||
// If enabling and no data exists, fetch it now
|
||||
if (enabled && !_service.hasData) {
|
||||
await _fetchData();
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Manually refresh the data
|
||||
Future<bool> refreshData({
|
||||
void Function(String message, double? progress)? onProgress,
|
||||
}) async {
|
||||
/// Manually refresh the data (force refresh)
|
||||
Future<bool> refreshData() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final success = await _service.refreshData(onProgress: onProgress);
|
||||
final success = await _service.forceRefresh();
|
||||
return success;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method to fetch data if needed with loading state management
|
||||
Future<bool> _fetchData() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final success = await _service.fetchDataIfNeeded();
|
||||
return success;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
|
||||
Reference in New Issue
Block a user