mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-07 05:07:54 +02:00
builds, needs work
This commit is contained in:
@@ -5,6 +5,7 @@ enum CameraIconType {
|
||||
real, // Blue ring - real cameras from OSM
|
||||
mock, // White ring - add camera mock point
|
||||
pending, // Purple ring - submitted/pending cameras
|
||||
editing, // Orange ring - camera being edited
|
||||
}
|
||||
|
||||
/// Simple camera icon with grey dot and colored ring
|
||||
@@ -21,6 +22,8 @@ class CameraIcon extends StatelessWidget {
|
||||
return kCameraRingColorMock;
|
||||
case CameraIconType.pending:
|
||||
return kCameraRingColorPending;
|
||||
case CameraIconType.editing:
|
||||
return kCameraRingColorEditing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import '../app_state.dart';
|
||||
import 'edit_camera_sheet.dart';
|
||||
|
||||
class CameraTagSheet extends StatelessWidget {
|
||||
final OsmCameraNode node;
|
||||
@@ -8,6 +11,25 @@ class CameraTagSheet extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
|
||||
// Check if this camera is editable (not a pending upload)
|
||||
final isEditable = !node.tags.containsKey('_pending_upload') ||
|
||||
node.tags['_pending_upload'] != 'true';
|
||||
|
||||
void _openEditSheet() {
|
||||
Navigator.pop(context); // Close this sheet first
|
||||
appState.startEditSession(node);
|
||||
final session = appState.editSession!;
|
||||
|
||||
// Show the edit sheet
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => EditCameraSheet(session: session),
|
||||
showDragHandle: true,
|
||||
);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
||||
@@ -46,13 +68,26 @@ class CameraTagSheet extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (isEditable) ...[
|
||||
ElevatedButton.icon(
|
||||
onPressed: _openEditSheet,
|
||||
icon: const Icon(Icons.edit, size: 18),
|
||||
label: const Text('Edit'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(0, 36),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../app_state.dart';
|
||||
import '../models/camera_profile.dart';
|
||||
|
||||
class EditCameraSheet extends StatelessWidget {
|
||||
const EditCameraSheet({super.key, required this.session});
|
||||
|
||||
final EditCameraSession session;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
|
||||
void _commit() {
|
||||
appState.commitEditSession();
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Camera edit queued for upload')),
|
||||
);
|
||||
}
|
||||
|
||||
void _cancel() {
|
||||
appState.cancelEditSession();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList();
|
||||
final allowSubmit = submittableProfiles.isNotEmpty && session.profile.isSubmittable;
|
||||
|
||||
return Padding(
|
||||
padding:
|
||||
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade400,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Edit Camera #${session.originalNode.id}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: const Text('Profile'),
|
||||
trailing: DropdownButton<CameraProfile>(
|
||||
value: session.profile,
|
||||
items: submittableProfiles
|
||||
.map((p) => DropdownMenuItem(value: p, child: Text(p.name)))
|
||||
.toList(),
|
||||
onChanged: (p) =>
|
||||
appState.updateEditSession(profile: p ?? session.profile),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Direction ${session.directionDegrees.round()}°'),
|
||||
subtitle: Slider(
|
||||
min: 0,
|
||||
max: 359,
|
||||
divisions: 359,
|
||||
value: session.directionDegrees,
|
||||
label: session.directionDegrees.round().toString(),
|
||||
onChanged: (v) => appState.updateEditSession(directionDeg: v),
|
||||
),
|
||||
),
|
||||
if (submittableProfiles.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.info_outline, color: Colors.red, size: 20),
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Enable a submittable profile in Settings to edit cameras.',
|
||||
style: TextStyle(color: Colors.red, fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else if (!session.profile.isSubmittable)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.info_outline, color: Colors.orange, size: 20),
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'This profile is for map viewing only. Please select a submittable profile to edit cameras.',
|
||||
style: TextStyle(color: Colors.orange, fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _cancel,
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: allowSubmit ? _commit : null,
|
||||
child: const Text('Save Edit'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ class MapOverlays extends StatelessWidget {
|
||||
final MapController mapController;
|
||||
final UploadMode uploadMode;
|
||||
final AddCameraSession? session;
|
||||
final EditCameraSession? editSession;
|
||||
final String? attribution; // Attribution for current tile provider
|
||||
|
||||
const MapOverlays({
|
||||
@@ -18,6 +19,7 @@ class MapOverlays extends StatelessWidget {
|
||||
required this.mapController,
|
||||
required this.uploadMode,
|
||||
this.session,
|
||||
this.editSession,
|
||||
this.attribution,
|
||||
});
|
||||
|
||||
@@ -130,13 +132,15 @@ class MapOverlays extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// Fixed pin when adding camera
|
||||
if (session != null)
|
||||
// Fixed pin when adding or editing camera
|
||||
if (session != null || editSession != null)
|
||||
IgnorePointer(
|
||||
child: Center(
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, kAddPinYOffset),
|
||||
child: const CameraIcon(type: CameraIconType.mock),
|
||||
child: CameraIcon(
|
||||
type: editSession != null ? CameraIconType.editing : CameraIconType.mock
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -225,6 +225,7 @@ class MapViewState extends State<MapView> {
|
||||
Widget build(BuildContext context) {
|
||||
final appState = context.watch<AppState>();
|
||||
final session = appState.session;
|
||||
final editSession = appState.editSession;
|
||||
|
||||
// Check if enabled profiles changed and refresh cameras if needed
|
||||
final currentEnabledProfiles = appState.enabledProfiles;
|
||||
@@ -272,6 +273,17 @@ class MapViewState extends State<MapView> {
|
||||
);
|
||||
} catch (_) {/* controller not ready yet */}
|
||||
}
|
||||
|
||||
// For edit sessions, center the map on the camera being edited initially
|
||||
if (editSession != null && _controller.camera.center != editSession.target) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
try {
|
||||
_controller.move(editSession.target, _controller.camera.zoom);
|
||||
} catch (_) {/* controller not ready yet */}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final zoom = _safeZoom();
|
||||
// Fetch cached cameras for current map bounds (using Consumer so overlays redraw instantly)
|
||||
@@ -323,6 +335,9 @@ class MapViewState extends State<MapView> {
|
||||
if (session != null) {
|
||||
appState.updateSession(target: pos.center);
|
||||
}
|
||||
if (editSession != null) {
|
||||
appState.updateEditSession(target: pos.center);
|
||||
}
|
||||
|
||||
// Show waiting indicator when map moves (user is expecting new content)
|
||||
NetworkStatus.instance.setWaiting();
|
||||
@@ -365,6 +380,7 @@ class MapViewState extends State<MapView> {
|
||||
mapController: _controller,
|
||||
uploadMode: appState.uploadMode,
|
||||
session: session,
|
||||
editSession: editSession,
|
||||
attribution: appState.selectedTileType?.attribution,
|
||||
),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user