From a12328377e01557171366656d81ee13137ac2135 Mon Sep 17 00:00:00 2001 From: yihong1120 Date: Sun, 14 Jan 2024 01:13:46 +0800 Subject: [PATCH 1/4] Utilise lazy loading to fetch marker details --- lib/screens/map/home_map.dart | 94 +++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/lib/screens/map/home_map.dart b/lib/screens/map/home_map.dart index 8e0ac0c..58a6bb3 100644 --- a/lib/screens/map/home_map.dart +++ b/lib/screens/map/home_map.dart @@ -1,7 +1,8 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; -// import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:logger/logger.dart'; +import 'package:http/http.dart' as http; var logger = Logger(); @@ -46,29 +47,80 @@ class _HomeMapPageState extends State { } void _loadMarkers() async { - // 此處模擬從後端加載標記數據 - // 您需要根據您的API替換這部分代碼 - // 假設從API獲取到的數據是一個包含經緯度和標題的列表 + try { + var url = Uri.parse('http://127.0.0.1:8000/api/traffic-violation-markers/'); + var response = await http.get(url); - List> mockData = [ - {"lat": 23.6978, "lng": 120.9605, "title": "違規地點1"}, - // ... 其他數據 - ]; + if (response.statusCode == 200) { + List data = json.decode(response.body); - setState(() { - _markers.clear(); - for (var markerData in mockData) { - final marker = Marker( - markerId: MarkerId(markerData['title']), - position: LatLng(markerData['lat'], markerData['lng']), - infoWindow: InfoWindow( - title: markerData['title'], - snippet: '點擊查看詳情', - ), - ); - _markers.add(marker); + setState(() { + _markers.clear(); + for (var markerData in data) { + final marker = Marker( + markerId: MarkerId(markerData['traffic_violation_id']), + position: LatLng(markerData['lat'], markerData['lng']), + onTap: () => _onMarkerTapped(markerData['traffic_violation_id']), + ); + _markers.add(marker); + } + }); + } else { + logger.e('Failed to load markers. Status code: ${response.statusCode}'); } - }); + } catch (e) { + logger.e('Error loading markers: $e'); + } + } + + void _onMarkerTapped(String trafficViolationId) async { + var url = Uri.parse('http://127.0.0.1:8000/api/traffic-violation-details/$trafficViolationId/'); + try { + var response = await http.get(url); + if (response.statusCode == 200) { + // 确保解析为 UTF-8 编码 + var decodedData = utf8.decode(response.bodyBytes); + var data = json.decode(decodedData); + // print(data); + setState(() { + var newMarkers = {}; + for (var marker in _markers) { + if (marker.markerId.value == trafficViolationId) { + String title = '${data['license_plate']?.toString() ?? '未知'} - ${data['violation']?.toString() ?? '未知'}'; + String snippet = '车牌号: ${data['license_plate']?.toString() ?? '未知'}\n' + '违章: ${data['violation']?.toString() ?? '未知'}\n' + '日期: ${data['date']?.toString() ?? '未知'}\n' + '时间: ${data['time']?.toString() ?? '未知'}\n' + '地址: ${data['address']?.toString() ?? '未知'}\n' + '官员: ${data['officer']?.toString() ?? '未知'}'; // 当 data['officer'] 为空时,显示 '未知' + + + print(snippet); + newMarkers.add( + Marker( + markerId: marker.markerId, + position: marker.position, + infoWindow: InfoWindow( + title: title, + snippet: snippet, + ), + onTap: () => _onMarkerTapped(marker.markerId.value), // 保持原有的 onTap 行为 + ), + ); + } else { + newMarkers.add(marker); + } + } + + _markers.clear(); + _markers.addAll(newMarkers); + }); + } else { + logger.e('Failed to load traffic violation details. Status code: ${response.statusCode}'); + } + } catch (e) { + logger.e('Error loading traffic violation details: $e'); + } } void _searchData() async { From 0082c09c2f7976af2d892a33054414bacd17960b Mon Sep 17 00:00:00 2001 From: yihong1120 Date: Sun, 14 Jan 2024 15:02:41 +0800 Subject: [PATCH 2/4] Demonstrate report details on map --- lib/screens/map/home_map.dart | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/screens/map/home_map.dart b/lib/screens/map/home_map.dart index 58a6bb3..425765d 100644 --- a/lib/screens/map/home_map.dart +++ b/lib/screens/map/home_map.dart @@ -48,20 +48,36 @@ class _HomeMapPageState extends State { void _loadMarkers() async { try { + // 指定 API URL var url = Uri.parse('http://127.0.0.1:8000/api/traffic-violation-markers/'); + // 发送请求并等待响应 var response = await http.get(url); if (response.statusCode == 200) { + // 解析响应数据 List data = json.decode(response.body); setState(() { _markers.clear(); for (var markerData in data) { + // 安全地获取数据,并为缺失的字段提供默认值 + String licensePlate = markerData['license_plate']?.toString() ?? '未知'; + String violation = markerData['violation']?.toString() ?? '未知'; + String date = markerData['date']?.toString() ?? '未知'; + double lat = markerData['lat'] != null ? markerData['lat'].toDouble() : 0.0; + double lng = markerData['lng'] != null ? markerData['lng'].toDouble() : 0.0; + + // 创建标记 final marker = Marker( - markerId: MarkerId(markerData['traffic_violation_id']), - position: LatLng(markerData['lat'], markerData['lng']), - onTap: () => _onMarkerTapped(markerData['traffic_violation_id']), + markerId: MarkerId(markerData['traffic_violation_id'].toString()), + position: LatLng(lat, lng), + infoWindow: InfoWindow( + title: '$licensePlate - $violation', + snippet: '车牌号: $licensePlate\n违章: $violation\n日期: $date', + ), + onTap: () => _onMarkerTapped(markerData['traffic_violation_id'].toString()), ); + // 将标记添加到地图上 _markers.add(marker); } }); @@ -92,7 +108,7 @@ class _HomeMapPageState extends State { '日期: ${data['date']?.toString() ?? '未知'}\n' '时间: ${data['time']?.toString() ?? '未知'}\n' '地址: ${data['address']?.toString() ?? '未知'}\n' - '官员: ${data['officer']?.toString() ?? '未知'}'; // 当 data['officer'] 为空时,显示 '未知' + '官员: ${data['officer']?.toString() ?? '未知'}'; print(snippet); From 92fd5a9bd83050c221d7cef6d1671fb49a983061 Mon Sep 17 00:00:00 2001 From: yihong1120 Date: Sun, 14 Jan 2024 15:47:46 +0800 Subject: [PATCH 3/4] Refactor the code and move the navigator drawer to lib/components/navigation_drawer.dart --- lib/components/navigation_drawer.dart | 50 ++++++++++++++--------- lib/main.dart | 15 ++++--- lib/screens/map/home_map.dart | 57 ++------------------------- 3 files changed, 44 insertions(+), 78 deletions(-) diff --git a/lib/components/navigation_drawer.dart b/lib/components/navigation_drawer.dart index 08c5c60..ec792fb 100644 --- a/lib/components/navigation_drawer.dart +++ b/lib/components/navigation_drawer.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -class CustomNavigationDrawer extends StatelessWidget { - const CustomNavigationDrawer({super.key}); - +class NavigationDrawer extends StatelessWidget { + const NavigationDrawer({super.key}); + @override Widget build(BuildContext context) { return Drawer( @@ -14,7 +14,7 @@ class CustomNavigationDrawer extends StatelessWidget { color: Colors.blue, ), child: Text( - 'Navigation Menu', + '菜單', style: TextStyle( color: Colors.white, fontSize: 24, @@ -24,32 +24,46 @@ class CustomNavigationDrawer extends StatelessWidget { ListTile( leading: const Icon(Icons.home), title: const Text('Home'), - onTap: () => _navigateTo(context, '/'), // 导航到主页,根据实际路由调整 + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/home'); // 更新为正确的路由 + }, + ), + ListTile( + leading: const Icon(Icons.report), + title: const Text('Create Report'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/create'); // 更新为正确的路由 + }, ), ListTile( - leading: const Icon(Icons.bar_chart), - title: const Text('Reports'), - onTap: () => _navigateTo(context, '/reports'), // 调整为实际报告页面的路由 + leading: const Icon(Icons.edit), + title: const Text('Edit Report'), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/reports'); // 更新为正确的路由 + }, ), ListTile( leading: const Icon(Icons.chat), title: const Text('Chatbot'), - onTap: () => _navigateTo(context, '/chat'), // 调整为实际聊天页面的路由 + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/chat'); // 更新为正确的路由 + }, ), ListTile( leading: const Icon(Icons.account_circle), title: const Text('Accounts'), - onTap: () => _navigateTo(context, '/accounts'), // 调整为实际账户页面的路由 + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/accounts'); // 更新为正确的路由 + }, ), + // 您可以根据需要添加更多的菜单项 ], ), ); } - - void _navigateTo(BuildContext context, String route) { - Navigator.pop(context); // 关闭抽屉 - if (ModalRoute.of(context)?.settings.name != route) { - Navigator.pushNamed(context, route); - } - } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 3d51f60..f38e161 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,14 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:traffic_report_front_flutter/screens/map/home_map.dart'; import 'screens/map/routes.dart' as map_routes; import 'screens/reports/routes.dart' as reports_routes; import 'screens/chat/routes.dart' as chat_routes; import 'screens/accounts/routes.dart' as account_routes; -import 'screens/map/home_map.dart'; +import 'components/navigation_drawer.dart' as AppDrawer; Future main() async { - await dotenv.load(fileName: ".env"); // 加載.env文件 + await dotenv.load(fileName: ".env"); // 加载 .env 文件 runApp(const TrafficReportApp()); } @@ -22,15 +23,17 @@ class TrafficReportApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), - routes: { ...map_routes.mapRoutes, ...reports_routes.reportsRoutes, ...chat_routes.chatRoutes, ...account_routes.accountsRoutes, }, - // 將 HomeMapPage 設為首頁 - home: const HomeMapPage(), + home: Scaffold( + appBar: AppBar(title: const Text('Traffic Report System')), + drawer: const AppDrawer.NavigationDrawer(), // 使用 NavigationDrawer + body: const HomeMapPage(), // 设置 HomeMapPage 作为主体内容 + ), ); } } diff --git a/lib/screens/map/home_map.dart b/lib/screens/map/home_map.dart index 425765d..145e785 100644 --- a/lib/screens/map/home_map.dart +++ b/lib/screens/map/home_map.dart @@ -191,58 +191,8 @@ class _HomeMapPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('交通違規報告系統')), - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - const DrawerHeader( - decoration: BoxDecoration( - color: Colors.blue, - ), - child: Text('菜單'), - ), - ListTile( - title: const Text('Home'), - onTap: () { - // 導航到 Home 頁面 - Navigator.pop(context); - }, - ), - ListTile( - title: const Text('Create Report'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, '/create'); - }, - ), - ListTile( - title: const Text('Edit Report'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, '/reports'); - }, - ), - ListTile( - title: const Text('Chatbot'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, '/chat'); - }, - ), - ListTile( - title: const Text('Accounts'), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed(context, '/accounts'); - }, - ), - ], - ), - ), - body: Column( - children: [ + return Column( + children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -300,7 +250,6 @@ class _HomeMapPageState extends State { ), ), ], - ), - ); + ); } } \ No newline at end of file From 7338ab3919f86124f4717d678171a1db8b0c9a3e Mon Sep 17 00:00:00 2001 From: yihong1120 Date: Sun, 14 Jan 2024 15:48:33 +0800 Subject: [PATCH 4/4] Prevent TextEditingController and VideoPlayerController from being released multiple times, which is not allowed --- lib/screens/reports/create_report_screen.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/screens/reports/create_report_screen.dart b/lib/screens/reports/create_report_screen.dart index b852a78..007cee5 100644 --- a/lib/screens/reports/create_report_screen.dart +++ b/lib/screens/reports/create_report_screen.dart @@ -53,13 +53,16 @@ class CreateReportPageState extends State { @override void dispose() { + // 一次释放 TextEditingControllers _dateController.dispose(); _timeController.dispose(); - _videoControllers.forEach((_, controller) => controller.dispose()); + // 释放 VideoPlayerControllers + for (var controller in _videoControllers.values) { + controller.dispose(); + } _videoControllers.clear(); - _dateController.dispose(); - _timeController.dispose(); + super.dispose(); }