feat: introduce FixMate Flutter app and React dashboard

- Add Flutter app shell (FixMateApp/MainScreen) with tabs: Report, Map,
  My Reports, Settings
- Implement capture and review flow (image_picker, geolocator, deterministic
  mock AI), and local storage (SharedPreferences + photo files on mobile)
- Build Map screen with flutter_map, marker clustering, filters, legend,
  marker details, and external maps deeplink
- Add My Reports list (view details, cycle status, delete) and Settings
  (language toggle via Provider, diagnostics, clear all data)
- Introduce JSON i18n loader and LocaleProvider; add EN/BM assets
- Define models (Report, enums) and UI badges (severity, status)

- Add static React dashboard (Leaflet map with clustering, heatmap toggle,
  filters incl. date range, queue, detail drawer), i18n (EN/BM), and
  demo data

- Update build/config and platform setup:
  - Extend pubspec with required packages and register i18n assets
  - Android: add CAMERA and location permissions; pin NDK version
  - iOS: add usage descriptions for camera, photo library, location
  - Gradle properties tuned for Windows/UNC stability
  - Register desktop plugins (Linux/macOS/Windows)
  - .gitignore: ignore .kilocode
  - Overhaul README and replace sample widget test
This commit is contained in:
2025-09-25 18:38:18 +08:00
parent d16e56bdcf
commit 6518df8ac1
39 changed files with 4377 additions and 162 deletions

View File

@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import '../../services/storage.dart';
import '../../models/report.dart';
import '../../widgets/report_card.dart';
import '../map/map_screen.dart';
import '../../l10n/i18n.dart';
class MyReportsScreen extends StatefulWidget {
const MyReportsScreen({super.key});
@override
State<MyReportsScreen> createState() => _MyReportsScreenState();
}
class _MyReportsScreenState extends State<MyReportsScreen> {
List<Report> _reports = [];
bool _loading = true;
@override
void initState() {
super.initState();
_loadReports();
}
Future<void> _loadReports() async {
setState(() {
_loading = true;
});
final reports = await StorageService.getReports();
setState(() {
_reports = reports.reversed.toList(); // newest first
_loading = false;
});
}
void _onViewReport(Report r) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => MapReportDetails(report: r)),
);
}
void _onDeleted() async {
await _loadReports();
}
void _onUpdated(Report updated) async {
await _loadReports();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(I18n.t('nav.myReports')),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadReports,
)
],
),
body: _loading
? const Center(child: CircularProgressIndicator())
: _reports.isEmpty
? Center(child: Text(I18n.t('map.noReports')))
: RefreshIndicator(
onRefresh: _loadReports,
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _reports.length,
itemBuilder: (context, index) {
final r = _reports[index];
return ReportCard(
report: r,
onView: () => _onViewReport(r),
onDeleted: _onDeleted,
onUpdated: _onUpdated,
);
},
),
),
);
}
}