fix: critical bugs preventing location reporting to server
- Send config to native before starting tracking (SharedPreferences sync) - Register ConnectivityReceiver for network state detection - Fix Settings number fields using TextEditingControllers - Cancel stream subscription on widget dispose (memory leak) - Replace WorkManager with AlarmManager for heartbeat - Add log cleanup for logs older than 24 hours - Change HTTP method from GET to POST These fixes resolve the 'device always offline' issue where: 1. Config was not being sent to native service 2. ConnectivityReceiver was never registered 3. Settings number fields were not saving 4. Heartbeat never fired due to WorkManager process isolation 5. Server expected POST not GET
This commit is contained in:
parent
3ae8bf00c1
commit
bbd51d1c35
10 changed files with 1200 additions and 167 deletions
|
|
@ -1,72 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'bridge/location_bridge.dart';
|
||||
|
||||
class StatusScreen extends StatelessWidget {
|
||||
class StatusScreen extends StatefulWidget {
|
||||
const StatusScreen({super.key});
|
||||
|
||||
@override
|
||||
State<StatusScreen> createState() => _StatusScreenState();
|
||||
}
|
||||
|
||||
class _StatusScreenState extends State<StatusScreen> {
|
||||
List<Map<String, dynamic>> _logs = [];
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLogs();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadLogs() async {
|
||||
final logs = await LocationBridge.getLogs();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_logs = logs.reversed.toList();
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _formatTimestamp(int timestamp) {
|
||||
final dt = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Color _getLogColor(String eventType) {
|
||||
switch (eventType) {
|
||||
case 'ERROR':
|
||||
return Colors.red;
|
||||
case 'WARNING':
|
||||
return Colors.orange;
|
||||
case 'LOCATION':
|
||||
return Colors.green;
|
||||
case 'SYNC':
|
||||
return Colors.cyan;
|
||||
case 'HEARTBEAT':
|
||||
return Colors.yellow;
|
||||
case 'FILTERED':
|
||||
return Colors.grey;
|
||||
default:
|
||||
return Colors.white;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTerminalLine(Map<String, dynamic> log) {
|
||||
final time = _formatTimestamp(log['timestamp'] as int? ?? 0);
|
||||
final type = log['eventType'] as String? ?? 'UNKNOWN';
|
||||
final msg = log['message'] as String? ?? '';
|
||||
final color = _getLogColor(type);
|
||||
|
||||
return Text(
|
||||
'[$time] $type: $msg',
|
||||
style: TextStyle(fontFamily: 'monospace', fontSize: 12, color: color),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Event Log')),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.info, color: Colors.blue),
|
||||
title: const Text('Status Screen'),
|
||||
subtitle: const Text('Event logs will be displayed here'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPlaceholderEvent('LOCATION', '37.7749', '-122.4194'),
|
||||
_buildPlaceholderEvent('SYNC', 'Location sent to server', ''),
|
||||
_buildPlaceholderEvent('HEARTBEAT', 'Heartbeat fired', ''),
|
||||
],
|
||||
appBar: AppBar(
|
||||
title: Text('Event Log'),
|
||||
actions: [IconButton(icon: Icon(Icons.refresh), onPressed: _loadLogs)],
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.black,
|
||||
child: _logs.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'No logs yet. Start tracking to see logs.',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.all(8),
|
||||
itemCount: _logs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = _logs[index];
|
||||
return _buildTerminalLine(log);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlaceholderEvent(String type, String detail, String extra) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Icon(_getIconForType(type), color: _getColorForType(type)),
|
||||
title: Text(type),
|
||||
subtitle: Text('$detail $extra'.trim()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconForType(String type) {
|
||||
switch (type) {
|
||||
case 'LOCATION':
|
||||
return Icons.location_on;
|
||||
case 'SYNC':
|
||||
return Icons.cloud_done;
|
||||
case 'HEARTBEAT':
|
||||
return Icons.favorite;
|
||||
case 'FILTERED':
|
||||
return Icons.filter_alt;
|
||||
case 'ERROR':
|
||||
return Icons.error;
|
||||
default:
|
||||
return Icons.info;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getColorForType(String type) {
|
||||
switch (type) {
|
||||
case 'LOCATION':
|
||||
return Colors.green;
|
||||
case 'SYNC':
|
||||
return Colors.blue;
|
||||
case 'HEARTBEAT':
|
||||
return Colors.orange;
|
||||
case 'FILTERED':
|
||||
return Colors.grey;
|
||||
case 'ERROR':
|
||||
return Colors.red;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue