- 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
134 lines
4.1 KiB
Dart
134 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../l10n/i18n.dart';
|
|
import '../../l10n/locale_provider.dart';
|
|
import '../../services/storage.dart';
|
|
|
|
class SettingsScreen extends StatefulWidget {
|
|
const SettingsScreen({super.key});
|
|
|
|
@override
|
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
}
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
|
StorageStats? _stats;
|
|
bool _loadingStats = true;
|
|
bool _clearing = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadStats();
|
|
}
|
|
|
|
Future<void> _loadStats() async {
|
|
setState(() {
|
|
_loadingStats = true;
|
|
});
|
|
final stats = await StorageService.getStorageStats();
|
|
if (mounted) {
|
|
setState(() {
|
|
_stats = stats;
|
|
_loadingStats = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _confirmAndClearAll(BuildContext context) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: Text(I18n.t('confirm.clearData.title')),
|
|
content: Text(I18n.t('confirm.clearData.message')),
|
|
actions: [
|
|
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(I18n.t('btn.no'))),
|
|
TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text(I18n.t('btn.yes'))),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true) {
|
|
setState(() {
|
|
_clearing = true;
|
|
});
|
|
final ok = await StorageService.clearAllReports();
|
|
setState(() {
|
|
_clearing = false;
|
|
});
|
|
if (ok) {
|
|
await _loadStats();
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(I18n.t('toast.storageCleared'))));
|
|
}
|
|
} else {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to clear data')));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final localeProvider = Provider.of<LocaleProvider>(context);
|
|
final isEnglish = localeProvider.isEnglish;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(I18n.t('nav.settings')),
|
|
),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
ListTile(
|
|
title: Text(I18n.t('settings.language')),
|
|
subtitle: Text(isEnglish ? I18n.t('lang.en') : I18n.t('lang.ms')),
|
|
trailing: Switch(
|
|
value: isEnglish,
|
|
onChanged: (v) async {
|
|
// toggle language and reload translations
|
|
if (v) {
|
|
await localeProvider.setEnglish();
|
|
} else {
|
|
await localeProvider.setMalay();
|
|
}
|
|
await I18n.init(localeProvider.locale);
|
|
// Force rebuild
|
|
setState(() {});
|
|
},
|
|
),
|
|
),
|
|
const Divider(),
|
|
ListTile(
|
|
title: Text(I18n.t('settings.theme')),
|
|
subtitle: Text(I18n.t('settings.theme.light')),
|
|
trailing: const Icon(Icons.light_mode),
|
|
enabled: false,
|
|
),
|
|
const Divider(),
|
|
ListTile(
|
|
title: Text(I18n.t('settings.diagnostics')),
|
|
subtitle: _loadingStats
|
|
? const Text('Loading...')
|
|
: Text('${I18n.t('toast.reportSaved')}: ${_stats?.reportCount ?? 0} • ${_stats?.formattedPhotoSize ?? "0 B"}'),
|
|
),
|
|
const SizedBox(height: 8),
|
|
ElevatedButton.icon(
|
|
onPressed: _clearing ? null : () => _confirmAndClearAll(context),
|
|
icon: const Icon(Icons.delete_forever),
|
|
label: _clearing ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : Text(I18n.t('btn.clearAll')),
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text('App', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: 8),
|
|
ListTile(
|
|
title: Text(I18n.t('app.name')),
|
|
subtitle: Text('v1.0.0'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |