mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-20 01:47:26 +02:00
UX and bones of suspected locations
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'settings/sections/max_nodes_section.dart';
|
||||
import 'settings/sections/proximity_alerts_section.dart';
|
||||
import 'settings/sections/suspected_locations_section.dart';
|
||||
import 'settings/sections/tile_provider_section.dart';
|
||||
import 'settings/sections/network_status_section.dart';
|
||||
import '../services/localization_service.dart';
|
||||
@@ -25,6 +26,8 @@ class AdvancedSettingsScreen extends StatelessWidget {
|
||||
Divider(),
|
||||
ProximityAlertsSection(),
|
||||
Divider(),
|
||||
SuspectedLocationsSection(),
|
||||
Divider(),
|
||||
NetworkStatusSection(),
|
||||
Divider(),
|
||||
TileProviderSection(),
|
||||
|
||||
@@ -18,7 +18,9 @@ import '../widgets/download_area_dialog.dart';
|
||||
import '../widgets/measured_sheet.dart';
|
||||
import '../widgets/navigation_sheet.dart';
|
||||
import '../widgets/search_bar.dart';
|
||||
import '../widgets/suspected_location_sheet.dart';
|
||||
import '../models/osm_node.dart';
|
||||
import '../models/suspected_location.dart';
|
||||
import '../models/search_result.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
@@ -455,6 +457,52 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
});
|
||||
}
|
||||
|
||||
void openSuspectedLocationSheet(SuspectedLocation location) {
|
||||
final appState = context.read<AppState>();
|
||||
appState.selectSuspectedLocation(location);
|
||||
|
||||
// Start smooth centering animation simultaneously with sheet opening
|
||||
try {
|
||||
_mapController.animateTo(
|
||||
dest: location.centroid,
|
||||
zoom: _mapController.mapController.camera.zoom,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
} catch (_) {
|
||||
// Map controller not ready, fallback to immediate move
|
||||
try {
|
||||
_mapController.mapController.move(location.centroid, _mapController.mapController.camera.zoom);
|
||||
} catch (_) {
|
||||
// Controller really not ready, skip centering
|
||||
}
|
||||
}
|
||||
|
||||
final controller = _scaffoldKey.currentState!.showBottomSheet(
|
||||
(ctx) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom, // Only safe area, no keyboard
|
||||
),
|
||||
child: MeasuredSheet(
|
||||
onHeightChanged: (height) {
|
||||
setState(() {
|
||||
_tagSheetHeight = height + MediaQuery.of(context).padding.bottom;
|
||||
});
|
||||
},
|
||||
child: SuspectedLocationSheet(location: location),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Reset height and clear selection when sheet is dismissed
|
||||
controller.closed.then((_) {
|
||||
setState(() {
|
||||
_tagSheetHeight = 0.0;
|
||||
});
|
||||
appState.clearSuspectedLocationSelection();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
@@ -536,6 +584,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
sheetHeight: activeSheetHeight,
|
||||
selectedNodeId: _selectedNodeId,
|
||||
onNodeTap: openNodeTagSheet,
|
||||
onSuspectedLocationTap: openSuspectedLocationSheet,
|
||||
onSearchPressed: _onNavigationButtonPressed,
|
||||
onUserGesture: () {
|
||||
if (appState.followMeMode != FollowMeMode.off) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../../app_state.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
|
||||
class SuspectedLocationsSection extends StatelessWidget {
|
||||
const SuspectedLocationsSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: LocalizationService.instance,
|
||||
builder: (context, child) {
|
||||
final locService = LocalizationService.instance;
|
||||
final appState = context.watch<AppState>();
|
||||
final isEnabled = appState.suspectedLocationsEnabled;
|
||||
final isLoading = appState.suspectedLocationsLoading;
|
||||
final lastFetch = appState.suspectedLocationsLastFetch;
|
||||
|
||||
String getLastFetchText() {
|
||||
if (lastFetch == null) {
|
||||
return 'Never fetched';
|
||||
} else {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(lastFetch);
|
||||
if (diff.inDays > 0) {
|
||||
return '${diff.inDays} days ago';
|
||||
} else if (diff.inHours > 0) {
|
||||
return '${diff.inHours} hours ago';
|
||||
} else if (diff.inMinutes > 0) {
|
||||
return '${diff.inMinutes} minutes ago';
|
||||
} else {
|
||||
return 'Just now';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleRefresh() async {
|
||||
final success = await appState.refreshSuspectedLocations();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(success
|
||||
? 'Suspected locations updated successfully'
|
||||
: 'Failed to update suspected locations'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Suspected Locations',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Enable/disable switch
|
||||
ListTile(
|
||||
leading: const Icon(Icons.help_outline),
|
||||
title: const Text('Show Suspected Locations'),
|
||||
subtitle: const Text('Show question mark markers for suspected surveillance sites from utility permit data'),
|
||||
trailing: Switch(
|
||||
value: isEnabled,
|
||||
onChanged: (enabled) {
|
||||
appState.setSuspectedLocationsEnabled(enabled);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
if (isEnabled) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Last update time
|
||||
ListTile(
|
||||
leading: const Icon(Icons.schedule),
|
||||
title: const Text('Last Updated'),
|
||||
subtitle: Text(getLastFetchText()),
|
||||
trailing: isLoading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: handleRefresh,
|
||||
tooltip: 'Refresh now',
|
||||
),
|
||||
),
|
||||
|
||||
// Data info
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: const Text('Data Source'),
|
||||
subtitle: const Text('Utility permit data indicating potential surveillance infrastructure installation sites'),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user