From b9192067b2f95b9589c04b282ece3672df4f5f51 Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 12:47:37 +0200 Subject: [PATCH 01/11] feat(chat): add connectivity_plus dependency Co-Authored-By: Claude Opus 4.6 (1M context) --- pubspec.lock | 32 ++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 33 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index 566331f..8f12bde 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" + url: "https://pub.dev" + source: hosted + version: "2.1.0" cross_cache: dependency: transitive description: @@ -145,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" diffutil_dart: dependency: transitive description: @@ -749,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" objective_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 58d0419..b1e58b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: firebase_core: ^3.13.0 firebase_crashlytics: ^4.3.5 firebase_performance: ^0.10.0+12 + connectivity_plus: ^6.1.4 dev_dependencies: flutter_test: From e0a4ee7bd97c8472668de5f6f267c7cea1cb842f Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 12:49:12 +0200 Subject: [PATCH 02/11] feat(l10n): add connectivity warning strings for model download Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/l10n/app_de.arb | 8 +++++++- lib/l10n/app_en.arb | 17 ++++++++++++++++- lib/l10n/app_es.arb | 8 +++++++- lib/l10n/app_fr.arb | 8 +++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 22effc6..070e177 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -151,5 +151,11 @@ "settingsPerformanceTitle": "Leistungsüberwachung", "settingsPerformanceDescription": "Anonyme Leistungsdaten sammeln, um die App zu verbessern", - "settingsPerformanceChangeFailureSnackbar": "Leistungsüberwachung-Einstellung konnte nicht geändert werden. Bitte versuchen Sie es erneut." + "settingsPerformanceChangeFailureSnackbar": "Leistungsüberwachung-Einstellung konnte nicht geändert werden. Bitte versuchen Sie es erneut.", + + "chatModelDownloadNoConnectionTitle": "Keine Internetverbindung", + "chatModelDownloadNoConnectionBody": "Verbinde dich mit dem Internet, um das KI-Modell herunterzuladen.", + "chatModelDownloadMobileDataTitle": "Großer Download", + "chatModelDownloadMobileDataBody": "Der Download des KI-Modells ist groß. Eine WLAN-Verbindung wird empfohlen.", + "chatModelDownloadContinue": "Weiter" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e2299c1..7d4de15 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -461,5 +461,20 @@ "@settingsPerformanceDescription": { "description": "Subtitle explaining what performance monitoring does." }, "settingsPerformanceChangeFailureSnackbar": "Couldn't change performance monitoring setting. Please try again.", - "@settingsPerformanceChangeFailureSnackbar": { "description": "Shown when persisting the performance monitoring toggle fails." } + "@settingsPerformanceChangeFailureSnackbar": { "description": "Shown when persisting the performance monitoring toggle fails." }, + + "chatModelDownloadNoConnectionTitle": "No internet connection", + "@chatModelDownloadNoConnectionTitle": { "description": "Dialog title when there is no internet connection before model download." }, + + "chatModelDownloadNoConnectionBody": "Connect to the internet to download the AI model.", + "@chatModelDownloadNoConnectionBody": { "description": "Dialog body when there is no internet connection before model download." }, + + "chatModelDownloadMobileDataTitle": "Large download", + "@chatModelDownloadMobileDataTitle": { "description": "Dialog title warning about large download on mobile data." }, + + "chatModelDownloadMobileDataBody": "The AI model download is large. A WiFi connection is recommended.", + "@chatModelDownloadMobileDataBody": { "description": "Dialog body warning about large download on mobile data." }, + + "chatModelDownloadContinue": "Continue", + "@chatModelDownloadContinue": { "description": "Button label to proceed with download on mobile data." } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ac462ce..f2ee884 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -151,5 +151,11 @@ "settingsPerformanceTitle": "Monitoreo de rendimiento", "settingsPerformanceDescription": "Recopilar datos de rendimiento anónimos para mejorar la aplicación", - "settingsPerformanceChangeFailureSnackbar": "No se pudo cambiar la configuración de monitoreo de rendimiento. Inténtelo de nuevo." + "settingsPerformanceChangeFailureSnackbar": "No se pudo cambiar la configuración de monitoreo de rendimiento. Inténtelo de nuevo.", + + "chatModelDownloadNoConnectionTitle": "Sin conexión a internet", + "chatModelDownloadNoConnectionBody": "Conéctate a internet para descargar el modelo de IA.", + "chatModelDownloadMobileDataTitle": "Descarga grande", + "chatModelDownloadMobileDataBody": "La descarga del modelo de IA es grande. Se recomienda usar una conexión WiFi.", + "chatModelDownloadContinue": "Continuar" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ad2c708..bcea21b 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -151,5 +151,11 @@ "settingsPerformanceTitle": "Rapport de performance", "settingsPerformanceDescription": "Collecter des données de performance anonymes pour améliorer l'application", - "settingsPerformanceChangeFailureSnackbar": "Impossible de modifier le paramètre de performance. Veuillez réessayer." + "settingsPerformanceChangeFailureSnackbar": "Impossible de modifier le paramètre de performance. Veuillez réessayer.", + + "chatModelDownloadNoConnectionTitle": "Pas de connexion internet", + "chatModelDownloadNoConnectionBody": "Connectez-vous à internet pour télécharger le modèle IA.", + "chatModelDownloadMobileDataTitle": "Téléchargement volumineux", + "chatModelDownloadMobileDataBody": "Le téléchargement du modèle IA est volumineux. Une connexion WiFi est recommandée.", + "chatModelDownloadContinue": "Continuer" } From 013cc81c82511fe0743cae83a7eab7512ee16d76 Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 12:51:00 +0200 Subject: [PATCH 03/11] feat(chat): add connectivity check before model download Co-Authored-By: Claude Opus 4.6 (1M context) --- .../presentation/model_download_page.dart | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lib/features/chat/presentation/model_download_page.dart b/lib/features/chat/presentation/model_download_page.dart index bf17c32..8986f5e 100644 --- a/lib/features/chat/presentation/model_download_page.dart +++ b/lib/features/chat/presentation/model_download_page.dart @@ -1,3 +1,4 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cookmate/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gemma/flutter_gemma.dart'; @@ -24,12 +25,71 @@ class _ModelDownloadPageState extends ConsumerState { _startDownload(); } + /// Returns true if the download should proceed, false otherwise. + Future _checkConnectivity() async { + final results = await Connectivity().checkConnectivity(); + + if (results.contains(ConnectivityResult.none)) { + if (!mounted) return false; + final l10n = AppLocalizations.of(context); + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.chatModelDownloadNoConnectionTitle), + content: Text(l10n.chatModelDownloadNoConnectionBody), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n.ok), + ), + ], + ), + ); + return false; + } + + if (!results.contains(ConnectivityResult.wifi)) { + if (!mounted) return false; + final l10n = AppLocalizations.of(context); + final proceed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.chatModelDownloadMobileDataTitle), + content: Text(l10n.chatModelDownloadMobileDataBody), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(l10n.chatModelDownloadContinue), + ), + ], + ), + ); + return proceed ?? false; + } + + return true; + } + Future _startDownload() async { setState(() { _error = null; _progress = 0; }); + final shouldProceed = await _checkConnectivity(); + if (!shouldProceed) { + if (mounted) { + setState(() { + _error = 'connectivity'; + }); + } + return; + } + try { final model = await ref.read(chatModelPreferenceProvider.future); From 4fbe8a763698e9bbe32b4d3a3a6b1951520ffb1f Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 12:53:56 +0200 Subject: [PATCH 04/11] docs: add connectivity_plus to SPEC.md --- SPEC.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SPEC.md b/SPEC.md index ba4f972..d3bdc4f 100644 --- a/SPEC.md +++ b/SPEC.md @@ -37,6 +37,10 @@ - http (REST client for Cookidoo recipe search and detail APIs) +## Connectivity + +- connectivity_plus (network type detection before model download) + ## Chat UI - flutter_chat_ui (Flyer Chat v2 — message list, composer, streaming) From 5fc7d23a734631571d4620b08072caeaf9c6a5fc Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 12:55:39 +0200 Subject: [PATCH 05/11] docs: add design spec and implementation plan for connectivity warning --- ...nectivity-warning-before-model-download.md | 264 ++++++++++++++++++ ...ty-warning-before-model-download-design.md | 88 ++++++ 2 files changed, 352 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-26-connectivity-warning-before-model-download.md create mode 100644 docs/superpowers/specs/2026-04-26-connectivity-warning-before-model-download-design.md diff --git a/docs/superpowers/plans/2026-04-26-connectivity-warning-before-model-download.md b/docs/superpowers/plans/2026-04-26-connectivity-warning-before-model-download.md new file mode 100644 index 0000000..b818fa7 --- /dev/null +++ b/docs/superpowers/plans/2026-04-26-connectivity-warning-before-model-download.md @@ -0,0 +1,264 @@ +# Connectivity Warning Before Model Download — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Warn users before starting the LLM model download when they are on mobile data or have no internet connection. + +**Architecture:** A one-shot connectivity check using `connectivity_plus` in `ModelDownloadPage._startDownload()`, gating the download behind an informative dialog. No new services, providers, or streams. + +**Tech Stack:** connectivity_plus, Flutter Material dialogs, ARB localization + +--- + +### Task 1: Add connectivity_plus dependency + +**Files:** +- Modify: `pubspec.yaml:56` (add dependency) + +- [ ] **Step 1: Add the package** + +In `pubspec.yaml`, add `connectivity_plus` after `firebase_performance`: + +```yaml + firebase_performance: ^0.10.0+12 + connectivity_plus: ^6.1.4 +``` + +- [ ] **Step 2: Install** + +Run: `flutter pub get` +Expected: resolves successfully, no version conflicts. + +- [ ] **Step 3: Commit** + +``` +feat(chat): add connectivity_plus dependency +``` + +--- + +### Task 2: Add localization strings + +**Files:** +- Modify: `lib/l10n/app_en.arb:464-465` +- Modify: `lib/l10n/app_es.arb:154-155` +- Modify: `lib/l10n/app_fr.arb:154-155` +- Modify: `lib/l10n/app_de.arb:154-155` + +Five new keys are needed. Existing keys `cancel`, `ok` are reused. + +- [ ] **Step 1: Add English strings** + +In `lib/l10n/app_en.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "chatModelDownloadNoConnectionTitle": "No internet connection", + "@chatModelDownloadNoConnectionTitle": { "description": "Dialog title when there is no internet connection before model download." }, + + "chatModelDownloadNoConnectionBody": "Connect to the internet to download the AI model.", + "@chatModelDownloadNoConnectionBody": { "description": "Dialog body when there is no internet connection before model download." }, + + "chatModelDownloadMobileDataTitle": "Large download", + "@chatModelDownloadMobileDataTitle": { "description": "Dialog title warning about large download on mobile data." }, + + "chatModelDownloadMobileDataBody": "The AI model download is large. A WiFi connection is recommended.", + "@chatModelDownloadMobileDataBody": { "description": "Dialog body warning about large download on mobile data." }, + + "chatModelDownloadContinue": "Continue", + "@chatModelDownloadContinue": { "description": "Button label to proceed with download on mobile data." } +``` + +- [ ] **Step 2: Add Spanish strings** + +In `lib/l10n/app_es.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "chatModelDownloadNoConnectionTitle": "Sin conexión a internet", + "chatModelDownloadNoConnectionBody": "Conéctate a internet para descargar el modelo de IA.", + "chatModelDownloadMobileDataTitle": "Descarga grande", + "chatModelDownloadMobileDataBody": "La descarga del modelo de IA es grande. Se recomienda usar una conexión WiFi.", + "chatModelDownloadContinue": "Continuar" +``` + +- [ ] **Step 3: Add French strings** + +In `lib/l10n/app_fr.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "chatModelDownloadNoConnectionTitle": "Pas de connexion internet", + "chatModelDownloadNoConnectionBody": "Connectez-vous à internet pour télécharger le modèle IA.", + "chatModelDownloadMobileDataTitle": "Téléchargement volumineux", + "chatModelDownloadMobileDataBody": "Le téléchargement du modèle IA est volumineux. Une connexion WiFi est recommandée.", + "chatModelDownloadContinue": "Continuer" +``` + +- [ ] **Step 4: Add German strings** + +In `lib/l10n/app_de.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "chatModelDownloadNoConnectionTitle": "Keine Internetverbindung", + "chatModelDownloadNoConnectionBody": "Verbinde dich mit dem Internet, um das KI-Modell herunterzuladen.", + "chatModelDownloadMobileDataTitle": "Großer Download", + "chatModelDownloadMobileDataBody": "Der Download des KI-Modells ist groß. Eine WLAN-Verbindung wird empfohlen.", + "chatModelDownloadContinue": "Weiter" +``` + +- [ ] **Step 5: Regenerate localizations** + +Run: `flutter gen-l10n` +Expected: completes without errors, generates updated `AppLocalizations`. + +- [ ] **Step 6: Commit** + +``` +feat(l10n): add connectivity warning strings for model download +``` + +--- + +### Task 3: Add connectivity check and dialogs to ModelDownloadPage + +**Files:** +- Modify: `lib/features/chat/presentation/model_download_page.dart` + +- [ ] **Step 1: Add imports** + +At the top of `model_download_page.dart`, add the `connectivity_plus` import: + +```dart +import 'package:connectivity_plus/connectivity_plus.dart'; +``` + +- [ ] **Step 2: Add the connectivity check method** + +Inside `_ModelDownloadPageState`, add a new method after `_startDownload()`: + +```dart + /// Returns true if the download should proceed, false otherwise. + Future _checkConnectivity() async { + final results = await Connectivity().checkConnectivity(); + + if (results.contains(ConnectivityResult.none)) { + if (!mounted) return false; + final l10n = AppLocalizations.of(context); + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.chatModelDownloadNoConnectionTitle), + content: Text(l10n.chatModelDownloadNoConnectionBody), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n.ok), + ), + ], + ), + ); + return false; + } + + if (!results.contains(ConnectivityResult.wifi)) { + if (!mounted) return false; + final l10n = AppLocalizations.of(context); + final proceed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.chatModelDownloadMobileDataTitle), + content: Text(l10n.chatModelDownloadMobileDataBody), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(l10n.chatModelDownloadContinue), + ), + ], + ), + ); + return proceed ?? false; + } + + return true; + } +``` + +- [ ] **Step 3: Gate the download behind the connectivity check** + +Modify `_startDownload()` to call `_checkConnectivity()` before downloading. Replace the current method body: + +```dart + Future _startDownload() async { + setState(() { + _error = null; + _progress = 0; + }); + + final shouldProceed = await _checkConnectivity(); + if (!shouldProceed) return; + + try { + final model = await ref.read(chatModelPreferenceProvider.future); + + await FlutterGemma.installModel( + modelType: model.modelType, + fileType: model.fileType, + ).fromNetwork(model.url).withProgress((progress) { + if (mounted) { + setState(() => _progress = progress); + } + }).install(); + + final storage = + await ref.read(chatModelPreferenceStorageProvider.future); + await storage.writeInstalled(model); + + if (mounted) { + widget.onComplete(); + } + } catch (e, stack) { + debugPrint('Model download failed: $e\n$stack'); + if (mounted) { + setState(() { + _error = e.toString(); + }); + } + } + } +``` + +- [ ] **Step 4: Verify the build compiles** + +Run: `flutter build apk --debug 2>&1 | tail -5` +Expected: `BUILD SUCCESSFUL` + +- [ ] **Step 5: Commit** + +``` +feat(chat): add connectivity check before model download +``` + +--- + +### Task 4: Update SPEC.md + +**Files:** +- Modify: `SPEC.md` + +- [ ] **Step 1: Add connectivity_plus to the Networking section** + +In `SPEC.md`, add a new section after **Cookidoo Integration** and before **Chat UI**: + +```markdown +## Connectivity + +- connectivity_plus (network type detection before model download) +``` + +- [ ] **Step 2: Commit** + +``` +docs: add connectivity_plus to SPEC.md +``` diff --git a/docs/superpowers/specs/2026-04-26-connectivity-warning-before-model-download-design.md b/docs/superpowers/specs/2026-04-26-connectivity-warning-before-model-download-design.md new file mode 100644 index 0000000..325e3b4 --- /dev/null +++ b/docs/superpowers/specs/2026-04-26-connectivity-warning-before-model-download-design.md @@ -0,0 +1,88 @@ +# Connectivity Warning Before Model Download + +## Problem + +The LLM model download (1.5–3 GB) starts automatically without checking network +conditions. Users on mobile data may unknowingly consume a large amount of their +data plan. Users with no connection see a generic error only after the download +fails. + +## Solution + +Before starting the model download, check the device's connectivity state and +show an informative dialog when conditions are not ideal. + +## Behavior + +### WiFi connected + +Download starts normally — no dialog shown. + +### Mobile data (no WiFi) + +An informative dialog is displayed: + +- **Title:** Large download warning (localized) +- **Body:** The model download is large. A WiFi connection is recommended. + (localized) +- **Actions:** "Cancel" (closes dialog, download does not start) / "Continue" + (closes dialog, download starts) + +The download does **not** start until the user taps "Continue". + +### No connection + +An informative dialog is displayed: + +- **Title:** No connection (localized) +- **Body:** No internet connection. Connect to the internet to download the + model. (localized) +- **Actions:** "OK" (closes dialog, download does not start) + +The download does not start. The user can tap "Retry" (existing button from +error state) to re-trigger the connectivity check. + +## Architecture + +### Package + +- `connectivity_plus` added to `pubspec.yaml` + +### Implementation + +All logic lives in `ModelDownloadPage._startDownload()`: + +1. Call `Connectivity().checkConnectivity()` to get the current connectivity + result. +2. If the result contains only `ConnectivityResult.none` → show "no connection" + dialog, return early. +3. If the result does not contain `ConnectivityResult.wifi` (i.e., mobile data + only) → show "large download" dialog. If user cancels, return early. +4. Otherwise (WiFi present) → proceed to download. + +No new service or provider is needed — this is a single check at a single call +site. + +### Localization + +New ARB keys added to all four locale files (`en`, `es`, `fr`, `de`): + +| Key | EN value | +|-----|----------| +| `chatModelDownloadNoConnectionTitle` | No internet connection | +| `chatModelDownloadNoConnectionBody` | Connect to the internet to download the AI model. | +| `chatModelDownloadMobileDataTitle` | Large download | +| `chatModelDownloadMobileDataBody` | The AI model download is large. A WiFi connection is recommended. | +| `chatModelDownloadContinue` | Continue | + +Existing keys reused: `cancel`, `ok`. + +### SPEC.md + +Add `connectivity_plus` to the listed dependencies. + +## What is NOT in scope + +- No continuous connectivity monitoring (stream) +- No internet quality / reachability verification +- No display of downloaded or total file size From bef118077d3f5b7aa510feab8102abc56c4b333c Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:02:47 +0200 Subject: [PATCH 06/11] fix(chat): prevent concurrent model downloads causing Future already completed crash Guard _startDownload() with a _downloading flag to prevent multiple simultaneous download attempts. Without this, rapid retry taps or page rebuilds could trigger overlapping flutter_gemma SmartDownloader instances, completing the same Future twice. Closes Crashlytics issue 8c0e50240cd828f1c248b2bdc9b57369. --- lib/features/chat/presentation/model_download_page.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/features/chat/presentation/model_download_page.dart b/lib/features/chat/presentation/model_download_page.dart index 8986f5e..5c00498 100644 --- a/lib/features/chat/presentation/model_download_page.dart +++ b/lib/features/chat/presentation/model_download_page.dart @@ -18,6 +18,7 @@ class ModelDownloadPage extends ConsumerStatefulWidget { class _ModelDownloadPageState extends ConsumerState { int _progress = 0; String? _error; + bool _downloading = false; @override void initState() { @@ -75,6 +76,8 @@ class _ModelDownloadPageState extends ConsumerState { } Future _startDownload() async { + if (_downloading) return; + _downloading = true; setState(() { _error = null; _progress = 0; @@ -82,6 +85,7 @@ class _ModelDownloadPageState extends ConsumerState { final shouldProceed = await _checkConnectivity(); if (!shouldProceed) { + _downloading = false; if (mounted) { setState(() { _error = 'connectivity'; @@ -116,6 +120,8 @@ class _ModelDownloadPageState extends ConsumerState { _error = e.toString(); }); } + } finally { + _downloading = false; } } From 6ca935a2aaa0ce1fb3fa3ac199919ba0b1a720bc Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:08:35 +0200 Subject: [PATCH 07/11] feat(l10n): add Italian locale Add app_it.arb with full Italian translations for all keys, register 'it' in both language name maps, and update SPEC.md to reflect 5 supported locales. --- SPEC.md | 2 +- .../chat/presentation/conversation_page.dart | 1 + .../presentation/language_picker_tile.dart | 1 + lib/l10n/app_it.arb | 161 ++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 lib/l10n/app_it.arb diff --git a/SPEC.md b/SPEC.md index d3bdc4f..08430b3 100644 --- a/SPEC.md +++ b/SPEC.md @@ -62,7 +62,7 @@ ## Internationalization -- flutter_localizations (ARB-based, 4 locales: en, fr, de, es) +- flutter_localizations (ARB-based, 5 locales: en, fr, de, es, it) ## Build & CI diff --git a/lib/features/chat/presentation/conversation_page.dart b/lib/features/chat/presentation/conversation_page.dart index a5a47eb..2e7dffc 100644 --- a/lib/features/chat/presentation/conversation_page.dart +++ b/lib/features/chat/presentation/conversation_page.dart @@ -58,6 +58,7 @@ class _ConversationPageState extends ConsumerState { 'en': 'English', 'es': 'Español', 'de': 'Deutsch', + 'it': 'Italiano', }; void _clearPendingAudio() { diff --git a/lib/features/l10n/presentation/language_picker_tile.dart b/lib/features/l10n/presentation/language_picker_tile.dart index 9b40857..001b36c 100644 --- a/lib/features/l10n/presentation/language_picker_tile.dart +++ b/lib/features/l10n/presentation/language_picker_tile.dart @@ -10,6 +10,7 @@ const Map _languageNames = { 'en': 'English', 'es': 'Español', 'de': 'Deutsch', + 'it': 'Italiano', }; class LanguagePickerTile extends ConsumerWidget { diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb new file mode 100644 index 0000000..b3f50c2 --- /dev/null +++ b/lib/l10n/app_it.arb @@ -0,0 +1,161 @@ +{ + "@@locale": "it", + "appTitle": "Cookmate", + "chatConversationsTitle": "Conversazioni", + "chatNewConversation": "Nuova conversazione", + "chatDeleteConversation": "Elimina conversazione", + "chatDeleteConfirmation": "Eliminare questa conversazione?", + "chatInputHint": "Scrivi un messaggio\u2026", + "chatEmptyState": "Nessuna conversazione. Tocca + per iniziare!", + "chatModelDownloadTitle": "Download del modello IA\u2026", + "chatModelDownloadProgress": "{progress}% completato", + "chatModelDownloadError": "Download fallito. Controlla la connessione e riprova.", + "chatModelDownloadRetry": "Riprova", + "settingsTitle": "Impostazioni", + "settingsSectionAi": "IA", + "settingsSectionGeneral": "Generali", + "settingsLanguageTitle": "Lingua", + "settingsLanguageFollowSystem": "Segui il sistema ({language})", + "settingsLanguageDialogTitle": "Scegli la lingua", + "settingsLanguageOptionSystem": "Segui il sistema", + "settingsLanguageChangeFailureSnackbar": "Impossibile cambiare la lingua. Riprova.", + "settingsThemeTitle": "Tema", + "settingsThemeDialogTitle": "Scegli il tema", + "settingsThemeOptionDark": "Scuro", + "settingsThemeOptionStandard": "Standard", + "settingsThemeOptionPink": "Rosa", + "settingsThemeOptionMatrix": "Matrix", + "settingsThemeChangeFailureSnackbar": "Impossibile cambiare il tema. Riprova.", + "settingsModelTitle": "Modello", + "settingsModelDialogTitle": "Scegli il modello IA", + "settingsModelChangeFailureSnackbar": "Impossibile cambiare il modello. Riprova.", + "settingsBackendTitle": "Acceleratore", + "settingsBackendDialogTitle": "Scegli il backend di inferenza", + "settingsBackendOptionGpu": "GPU (pi\u00f9 veloce, richiede un dispositivo compatibile)", + "settingsBackendOptionCpu": "CPU (pi\u00f9 lento, funziona ovunque)", + "settingsBackendChangeFailureSnackbar": "Impossibile cambiare il backend. Riprova.", + "settingsReasoningTitle": "Ragionamento", + "settingsReasoningSubtitleOn": "Attivato", + "settingsReasoningSubtitleOff": "Disattivato", + "settingsExpertTitle": "Esperto", + "settingsExpertDialogTitle": "Impostazioni esperto", + "settingsExpertMaxTokens": "Token massimi", + "settingsExpertMaxTokensInfo": "Numero massimo di token che il modello pu\u00f2 generare in una singola risposta. Valori pi\u00f9 alti consentono risposte pi\u00f9 lunghe ma utilizzano pi\u00f9 memoria.", + "settingsExpertTopK": "Top-K", + "settingsExpertTopKInfo": "Limita il modello alle K parole pi\u00f9 probabili. Valori bassi producono risposte pi\u00f9 precise; valori alti le rendono pi\u00f9 creative.", + "settingsExpertTopP": "Top-P", + "settingsExpertTopPInfo": "Campionamento a nucleo: il modello considera solo il pi\u00f9 piccolo insieme di parole la cui probabilit\u00e0 combinata supera P. Valori bassi producono un testo pi\u00f9 prevedibile.", + "settingsExpertTemperature": "Temperatura", + "settingsExpertTemperatureInfo": "Controlla la casualit\u00e0. Valori bassi (vicini a 0) producono risposte deterministiche e precise. Valori alti rendono il modello pi\u00f9 creativo e sorprendente.", + "settingsExpertTokenBuffer": "Token buffer", + "settingsExpertTokenBufferInfo": "Spazio riservato per la gestione del contesto. Valori elevati attivano la riduzione della cronologia prima, prevenendo la degenerazione nelle conversazioni lunghe. Intervallo: 256\u20134096.", + "settingsExpertReset": "Ripristina", + "settingsExpertSubtitle": "Token: {maxTokens} \u00b7 Temp: {temperature}", + "settingsExpertChangeFailureSnackbar": "Impossibile salvare le impostazioni esperto. Riprova.", + "settingsReasoningChangeFailureSnackbar": "Impossibile cambiare l'impostazione di ragionamento. Riprova.", + "chatTimeJustNow": "adesso", + "chatTimeMinutesAgo": "{minutes} min fa", + "chatTimeHoursAgo": "{hours} h fa", + "chatModelErrorBanner": "Errore modello: {error}", + "chatModelErrorRetry": "Riprova", + "chatModelLoading": "Il modello si sta caricando, attendere\u2026", + "chatAiInfoTitle": "Impostazioni IA", + "chatAiInfoModel": "Modello", + "chatAiInfoAccelerator": "Acceleratore", + "chatAiInfoReasoning": "Ragionamento", + "chatAiInfoMaxTokens": "Token massimi", + "chatAiInfoTemperature": "Temperatura", + "chatAiInfoTopK": "Top-K", + "chatAiInfoTopP": "Top-P", + "chatAiInfoTokenBuffer": "Token buffer", + "chatAiInfoClose": "Chiudi", + "chatThinkingLabel": "Sto pensando\u2026", + "cancel": "Annulla", + "delete": "Elimina", + "ok": "OK", + "homeTabChat": "Chat", + "homeTabSettings": "Impostazioni", + "splashTitle": "CookMate", + "splashDescription": "Crea le tue ricette Thermomix con l'assistente CookMate.", + + "chatAttachPhoto": "Foto", + "chatAttachGallery": "Galleria", + "chatAttachAudio": "Registra audio", + "chatRecordingInProgress": "Registrazione\u2026", + "chatImageCaption": "Immagine", + "chatAudioCaption": "Messaggio audio", + "chatAudioAttached": "Audio allegato", + "chatImageAttached": "Immagine allegata", + "chatMediaPermissionDenied": "Permesso negato. Consenti l'accesso nelle Impostazioni.", + "chatVisionUnavailable": "L'analisi delle immagini non \u00e8 disponibile su questo dispositivo.", + "chatImagePrompt": "Descrivi questa immagine e aiutami con questa ricetta.", + "chatAudioPrompt": "Trascrivi e rispondi a questo messaggio audio.", + "chatUserDisplayName": "Tu", + + "settingsSectionRecipe": "Ricetta", + "settingsTmVersionTitle": "Versione TM", + "settingsTmVersionDialogTitle": "Scegli la versione Thermomix", + "settingsTmVersionOptionTm5": "TM5", + "settingsTmVersionOptionTm6": "TM6", + "settingsTmVersionOptionTm7": "TM7", + "settingsUnitSystemTitle": "Sistema di unit\u00e0", + "settingsUnitSystemDialogTitle": "Scegli il sistema di unit\u00e0", + "settingsUnitSystemOptionMetric": "Metrico (g, ml, \u00b0C)", + "settingsUnitSystemOptionImperial": "Imperiale (oz, cups, \u00b0F)", + "settingsPortionsTitle": "Porzioni", + "settingsPortionsDialogTitle": "Scegli il numero di porzioni", + "settingsPortionsValue": "{count} porzioni", + "settingsLevelTitle": "Livello", + "settingsLevelDialogTitle": "Scegli il livello della ricetta", + "settingsLevelOptionBeginner": "Principiante", + "settingsLevelOptionIntermediate": "Intermedio", + "settingsLevelOptionAdvanced": "Avanzato", + "settingsLevelOptionAllLevels": "Tutti i livelli", + "settingsDietaryRestrictionsTitle": "Restrizioni alimentari", + "settingsDietaryRestrictionsDialogTitle": "Restrizioni alimentari", + "settingsDietaryRestrictionsHint": "es. senza glutine, vegetariano, senza frutta a guscio\u2026", + "settingsDietaryRestrictionsNone": "Nessuna", + "settingsRecipeChangeFailureSnackbar": "Impossibile salvare le impostazioni della ricetta. Riprova.", + "chatInfoTabRecipe": "Ricetta", + "chatInfoTabSkills": "Skills", + "chatInfoTabAi": "IA", + "chatRecipeInfoTmVersion": "Versione TM", + "chatRecipeInfoUnitSystem": "Sistema di unit\u00e0", + "chatRecipeInfoPortions": "Porzioni", + "chatRecipeInfoLevel": "Livello", + "chatRecipeInfoDietaryRestrictions": "Restrizioni alimentari", + "chatRecipeInfoLanguage": "Lingua", + "chatRenameConversation": "Rinomina conversazione", + "chatRenameHint": "Nome della conversazione", + "settingsSectionActions": "Azioni", + "settingsDeleteAllConversations": "Elimina tutte le conversazioni", + "settingsDeleteAllConversationsConfirmation": "Eliminare tutte le conversazioni? Questa azione non pu\u00f2 essere annullata.", + + "settingsSectionSkills": "Skills", + + "settingsSectionCookidoo": "Cookidoo", + "settingsCookidooEmailTitle": "Email", + "settingsCookidooEmailHint": "Email dell'account Cookidoo", + "settingsCookidooPasswordTitle": "Password", + "settingsCookidooPasswordHint": "Password dell'account Cookidoo", + "settingsCookidooTest": "Testa", + "settingsCookidooTestSuccess": "Connessione riuscita!", + "settingsCookidooTestFailure": "Connessione fallita. Controlla le tue credenziali.", + "settingsCookidooNotConfigured": "Non configurato", + "settingsCookidooChangeFailureSnackbar": "Impossibile salvare le credenziali Cookidoo. Riprova.", + + "settingsSectionObservability": "Osservabilit\u00e0", + "settingsCrashlyticsTitle": "Segnalazione errori", + "settingsCrashlyticsDescription": "Invia segnalazioni anonime di arresti anomali per migliorare l'app", + "settingsCrashlyticsChangeFailureSnackbar": "Impossibile modificare l'impostazione di segnalazione errori. Riprova.", + + "settingsPerformanceTitle": "Monitoraggio prestazioni", + "settingsPerformanceDescription": "Raccogliere dati anonimi sulle prestazioni per migliorare l'app", + "settingsPerformanceChangeFailureSnackbar": "Impossibile modificare l'impostazione di monitoraggio prestazioni. Riprova.", + + "chatModelDownloadNoConnectionTitle": "Nessuna connessione internet", + "chatModelDownloadNoConnectionBody": "Connettiti a internet per scaricare il modello IA.", + "chatModelDownloadMobileDataTitle": "Download di grandi dimensioni", + "chatModelDownloadMobileDataBody": "Il download del modello IA \u00e8 di grandi dimensioni. Si consiglia una connessione WiFi.", + "chatModelDownloadContinue": "Continua" +} From fdfff4ab0d1fc020f7adbd9f289815f2591d0ed3 Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:18:13 +0200 Subject: [PATCH 08/11] feat(l10n): add reset all settings strings Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/l10n/app_de.arb | 6 +++++- lib/l10n/app_en.arb | 11 ++++++++++- lib/l10n/app_es.arb | 6 +++++- lib/l10n/app_fr.arb | 6 +++++- lib/l10n/app_it.arb | 6 +++++- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 070e177..4912a3e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -157,5 +157,9 @@ "chatModelDownloadNoConnectionBody": "Verbinde dich mit dem Internet, um das KI-Modell herunterzuladen.", "chatModelDownloadMobileDataTitle": "Großer Download", "chatModelDownloadMobileDataBody": "Der Download des KI-Modells ist groß. Eine WLAN-Verbindung wird empfohlen.", - "chatModelDownloadContinue": "Weiter" + "chatModelDownloadContinue": "Weiter", + + "settingsResetAll": "Alle Einstellungen zurücksetzen", + "settingsResetAllConfirmation": "Alle Einstellungen zurücksetzen? Die App wird geschlossen und alle Daten werden gelöscht. Dies kann nicht rückgängig gemacht werden.", + "settingsResetAllButton": "Zurücksetzen" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7d4de15..5ee2ac7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -476,5 +476,14 @@ "@chatModelDownloadMobileDataBody": { "description": "Dialog body warning about large download on mobile data." }, "chatModelDownloadContinue": "Continue", - "@chatModelDownloadContinue": { "description": "Button label to proceed with download on mobile data." } + "@chatModelDownloadContinue": { "description": "Button label to proceed with download on mobile data." }, + + "settingsResetAll": "Reset all settings", + "@settingsResetAll": { "description": "Label for the reset all settings action in settings." }, + + "settingsResetAllConfirmation": "Reset all settings? The app will close and all data will be deleted. This cannot be undone.", + "@settingsResetAllConfirmation": { "description": "Confirmation prompt before resetting all settings." }, + + "settingsResetAllButton": "Reset", + "@settingsResetAllButton": { "description": "Button label to confirm reset all settings." } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f2ee884..c33dc1a 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -157,5 +157,9 @@ "chatModelDownloadNoConnectionBody": "Conéctate a internet para descargar el modelo de IA.", "chatModelDownloadMobileDataTitle": "Descarga grande", "chatModelDownloadMobileDataBody": "La descarga del modelo de IA es grande. Se recomienda usar una conexión WiFi.", - "chatModelDownloadContinue": "Continuar" + "chatModelDownloadContinue": "Continuar", + + "settingsResetAll": "Restablecer todos los ajustes", + "settingsResetAllConfirmation": "¿Restablecer todos los ajustes? La aplicación se cerrará y todos los datos serán eliminados. Esta acción no se puede deshacer.", + "settingsResetAllButton": "Restablecer" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index bcea21b..809f1a9 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -157,5 +157,9 @@ "chatModelDownloadNoConnectionBody": "Connectez-vous à internet pour télécharger le modèle IA.", "chatModelDownloadMobileDataTitle": "Téléchargement volumineux", "chatModelDownloadMobileDataBody": "Le téléchargement du modèle IA est volumineux. Une connexion WiFi est recommandée.", - "chatModelDownloadContinue": "Continuer" + "chatModelDownloadContinue": "Continuer", + + "settingsResetAll": "Réinitialiser tous les réglages", + "settingsResetAllConfirmation": "Réinitialiser tous les réglages ? L'application se fermera et toutes les données seront supprimées. Cette action est irréversible.", + "settingsResetAllButton": "Réinitialiser" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index b3f50c2..88db260 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -157,5 +157,9 @@ "chatModelDownloadNoConnectionBody": "Connettiti a internet per scaricare il modello IA.", "chatModelDownloadMobileDataTitle": "Download di grandi dimensioni", "chatModelDownloadMobileDataBody": "Il download del modello IA \u00e8 di grandi dimensioni. Si consiglia una connessione WiFi.", - "chatModelDownloadContinue": "Continua" + "chatModelDownloadContinue": "Continua", + + "settingsResetAll": "Ripristina tutte le impostazioni", + "settingsResetAllConfirmation": "Ripristinare tutte le impostazioni? L'app si chiuderà e tutti i dati verranno eliminati. Questa azione non può essere annullata.", + "settingsResetAllButton": "Ripristina" } From ffd71e229d34619d16a505da065dcb4f240d1d07 Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:19:39 +0200 Subject: [PATCH 09/11] feat(settings): add reset all settings action Co-Authored-By: Claude Opus 4.6 (1M context) --- .../settings/presentation/settings_page.dart | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/lib/features/settings/presentation/settings_page.dart b/lib/features/settings/presentation/settings_page.dart index a77a667..cad87b1 100644 --- a/lib/features/settings/presentation/settings_page.dart +++ b/lib/features/settings/presentation/settings_page.dart @@ -1,6 +1,13 @@ import 'package:cookmate/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_gemma/flutter_gemma.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:path/path.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:sqflite/sqflite.dart'; + +import '../../chat/domain/chat_model_preference.dart'; import '../../chat/presentation/backend_picker_tile.dart'; import '../../chat/presentation/expert_picker_tile.dart'; @@ -100,6 +107,20 @@ class SettingsPage extends ConsumerWidget { onTap: () => _confirmDeleteAll(context, ref), ), const Divider(height: 1), + ListTile( + leading: Icon( + Icons.restore, + color: Theme.of(context).colorScheme.error, + ), + title: Text( + l10n.settingsResetAll, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + onTap: () => _confirmResetAll(context, ref), + ), + const Divider(height: 1), ], ), ); @@ -132,4 +153,58 @@ class SettingsPage extends ConsumerWidget { await ref.read(conversationsProvider.notifier).deleteAll(); } } + + Future _confirmResetAll(BuildContext context, WidgetRef ref) async { + final l10n = AppLocalizations.of(context); + final confirmed = await showDialog( + context: context, + useRootNavigator: true, + builder: (ctx) => AlertDialog( + title: Text(l10n.settingsResetAll), + content: Text(l10n.settingsResetAllConfirmation), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: Text( + l10n.settingsResetAllButton, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + ], + ), + ); + if (confirmed != true) return; + + // 1. Read installed model before clearing preferences. + final prefs = await SharedPreferences.getInstance(); + final installedModelName = prefs.getString('chat_model_installed'); + + // 2. Clear all shared preferences. + await prefs.clear(); + + // 3. Uninstall downloaded model if one exists. + if (installedModelName != null) { + for (final model in ChatModelPreference.values) { + if (model.name == installedModelName) { + try { + await FlutterGemma.uninstallModel(model.fileName); + } catch (_) { + // Best-effort — model file may already be gone. + } + break; + } + } + } + + // 4. Delete SQLite database. + final dbPath = join(await getDatabasesPath(), 'cookmate_chat.db'); + await deleteDatabase(dbPath); + + // 5. Close the app. + await SystemNavigator.pop(); + } } From e2f1f06d82e4817c3851a0f817290c71f567d83a Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:20:54 +0200 Subject: [PATCH 10/11] docs: add design spec and implementation plan for reset all settings --- .../plans/2026-04-26-reset-all-settings.md | 207 ++++++++++++++++++ .../2026-04-26-reset-all-settings-design.md | 58 +++++ 2 files changed, 265 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-26-reset-all-settings.md create mode 100644 docs/superpowers/specs/2026-04-26-reset-all-settings-design.md diff --git a/docs/superpowers/plans/2026-04-26-reset-all-settings.md b/docs/superpowers/plans/2026-04-26-reset-all-settings.md new file mode 100644 index 0000000..8086ee0 --- /dev/null +++ b/docs/superpowers/plans/2026-04-26-reset-all-settings.md @@ -0,0 +1,207 @@ +# Reset All Settings — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a "Reset all settings" action that wipes all local data and closes the app. + +**Architecture:** A new list tile in the Settings Actions section triggers a confirmation dialog. On confirm, SharedPreferences are cleared, the downloaded model is uninstalled, the SQLite database is deleted, and the app closes via `SystemNavigator.pop()`. + +**Tech Stack:** SharedPreferences, sqflite (deleteDatabase), flutter_gemma (uninstallModel), Flutter services (SystemNavigator) + +--- + +### Task 1: Add localization strings + +**Files:** +- Modify: `lib/l10n/app_en.arb` +- Modify: `lib/l10n/app_es.arb` +- Modify: `lib/l10n/app_fr.arb` +- Modify: `lib/l10n/app_de.arb` +- Modify: `lib/l10n/app_it.arb` + +Three new keys. Existing key `cancel` is reused. + +- [ ] **Step 1: Add English strings** + +In `lib/l10n/app_en.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "settingsResetAll": "Reset all settings", + "@settingsResetAll": { "description": "Label for the reset all settings action in settings." }, + + "settingsResetAllConfirmation": "Reset all settings? The app will close and all data will be deleted. This cannot be undone.", + "@settingsResetAllConfirmation": { "description": "Confirmation prompt before resetting all settings." }, + + "settingsResetAllButton": "Reset", + "@settingsResetAllButton": { "description": "Button label to confirm reset all settings." } +``` + +- [ ] **Step 2: Add Spanish strings** + +In `lib/l10n/app_es.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "settingsResetAll": "Restablecer todos los ajustes", + "settingsResetAllConfirmation": "¿Restablecer todos los ajustes? La aplicación se cerrará y todos los datos serán eliminados. Esta acción no se puede deshacer.", + "settingsResetAllButton": "Restablecer" +``` + +- [ ] **Step 3: Add French strings** + +In `lib/l10n/app_fr.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "settingsResetAll": "Réinitialiser tous les réglages", + "settingsResetAllConfirmation": "Réinitialiser tous les réglages ? L'application se fermera et toutes les données seront supprimées. Cette action est irréversible.", + "settingsResetAllButton": "Réinitialiser" +``` + +- [ ] **Step 4: Add German strings** + +In `lib/l10n/app_de.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "settingsResetAll": "Alle Einstellungen zurücksetzen", + "settingsResetAllConfirmation": "Alle Einstellungen zurücksetzen? Die App wird geschlossen und alle Daten werden gelöscht. Dies kann nicht rückgängig gemacht werden.", + "settingsResetAllButton": "Zurücksetzen" +``` + +- [ ] **Step 5: Add Italian strings** + +In `lib/l10n/app_it.arb`, before the closing `}`, add a comma after the last entry and insert: + +```json + "settingsResetAll": "Ripristina tutte le impostazioni", + "settingsResetAllConfirmation": "Ripristinare tutte le impostazioni? L'app si chiuderà e tutti i dati verranno eliminati. Questa azione non può essere annullata.", + "settingsResetAllButton": "Ripristina" +``` + +- [ ] **Step 6: Regenerate localizations** + +Run: `flutter gen-l10n` +Expected: completes without errors. + +- [ ] **Step 7: Commit** + +``` +feat(l10n): add reset all settings strings +``` + +--- + +### Task 2: Add reset action to settings page + +**Files:** +- Modify: `lib/features/settings/presentation/settings_page.dart` + +- [ ] **Step 1: Add imports** + +At the top of `settings_page.dart`, add these imports: + +```dart +import 'package:flutter/services.dart'; +import 'package:flutter_gemma/flutter_gemma.dart'; +import 'package:path/path.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:sqflite/sqflite.dart'; +``` + +- [ ] **Step 2: Add the reset tile in the build method** + +In the `build` method, after the existing "Delete all conversations" `ListTile` (line 89-101) and before `const Divider(height: 1)` at line 102, insert a new tile: + +```dart + const Divider(height: 1), + ListTile( + leading: Icon( + Icons.restore, + color: Theme.of(context).colorScheme.error, + ), + title: Text( + l10n.settingsResetAll, + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + onTap: () => _confirmResetAll(context, ref), + ), +``` + +- [ ] **Step 3: Add the _confirmResetAll method** + +Add a new method after `_confirmDeleteAll` (after line 134), before the closing `}` of the class: + +```dart + Future _confirmResetAll(BuildContext context, WidgetRef ref) async { + final l10n = AppLocalizations.of(context); + final confirmed = await showDialog( + context: context, + useRootNavigator: true, + builder: (ctx) => AlertDialog( + title: Text(l10n.settingsResetAll), + content: Text(l10n.settingsResetAllConfirmation), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: Text( + l10n.settingsResetAllButton, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + ], + ), + ); + if (confirmed != true) return; + + // 1. Read installed model before clearing preferences. + final prefs = await SharedPreferences.getInstance(); + final installedModelName = prefs.getString('chat_model_installed'); + + // 2. Clear all shared preferences. + await prefs.clear(); + + // 3. Uninstall downloaded model if one exists. + if (installedModelName != null) { + for (final model in ChatModelPreference.values) { + if (model.name == installedModelName) { + try { + await FlutterGemma.uninstallModel(model.fileName); + } catch (_) { + // Best-effort — model file may already be gone. + } + break; + } + } + } + + // 4. Delete SQLite database. + final dbPath = join(await getDatabasesPath(), 'cookmate_chat.db'); + await deleteDatabase(dbPath); + + // 5. Close the app. + await SystemNavigator.pop(); + } +``` + +- [ ] **Step 4: Add the missing import for ChatModelPreference** + +Add this import at the top of the file: + +```dart +import '../../chat/domain/chat_model_preference.dart'; +``` + +- [ ] **Step 5: Verify the build compiles** + +Run: `flutter build apk --debug 2>&1 | tail -5` +Expected: `BUILD SUCCESSFUL` + +- [ ] **Step 6: Commit** + +``` +feat(settings): add reset all settings action +``` diff --git a/docs/superpowers/specs/2026-04-26-reset-all-settings-design.md b/docs/superpowers/specs/2026-04-26-reset-all-settings-design.md new file mode 100644 index 0000000..bd3e24f --- /dev/null +++ b/docs/superpowers/specs/2026-04-26-reset-all-settings-design.md @@ -0,0 +1,58 @@ +# Reset All Settings + +## Problem + +Users have no way to fully reset the app to its initial state. The only +destructive action available is "Delete all conversations", which leaves +preferences, downloaded models, and credentials intact. + +## Solution + +Add a "Reset all settings" action in the Settings Actions section that wipes +all local data and closes the app. + +## UI + +New list tile in Settings > Actions, below "Delete all conversations": + +- Icon: `Icons.restore`, red color +- Label: "Reset all settings" (localized) +- Tap shows a confirmation dialog: + - Body: "Reset all settings? The app will close and all data will be deleted. + This cannot be undone." (localized) + - Actions: "Cancel" (existing key) / "Reset" (localized, red/destructive) + +## Logic + +Executed sequentially after user confirms: + +1. **Clear SharedPreferences** — `SharedPreferences.getInstance()` then + `prefs.clear()`. Removes all key-value settings (theme, locale, model + preference, backend, reasoning, expert config, skills, Cookidoo credentials, + observability toggles). +2. **Uninstall downloaded model** — Read installed model from + `chatModelPreferenceStorageProvider`. If a model is installed, call + `FlutterGemma.uninstallModel(fileName)`. +3. **Delete SQLite database** — Call `deleteDatabase(path)` using the same + database path from `ChatDatabase` (`cookmate_chat.db`). +4. **Close the app** — `SystemNavigator.pop()`. + +All logic lives directly in `settings_page.dart` — no new service needed. + +## Localization + +New keys in all 5 locale files (en, es, fr, de, it): + +| Key | EN value | +|-----|----------| +| `settingsResetAll` | Reset all settings | +| `settingsResetAllConfirmation` | Reset all settings? The app will close and all data will be deleted. This cannot be undone. | +| `settingsResetAllButton` | Reset | + +Existing key reused: `cancel`. + +## What is NOT in scope + +- No selective reset (everything is wiped) +- No redirect to splash screen (app closes) +- No dedicated service or provider From 3548bcfcc01fabe8a55beabd1d6108b1d7e80e71 Mon Sep 17 00:00:00 2001 From: using-system Date: Sun, 26 Apr 2026 13:34:41 +0200 Subject: [PATCH 11/11] fix: address Copilot review feedback - Add 'it' to LocalePreference.supportedLanguageCodes so Italian is selectable in the language picker - Use Future.microtask for _startDownload in initState to avoid showing dialogs before the first frame - Set barrierDismissible: false on the no-connection dialog - Check explicitly for ConnectivityResult.mobile instead of !wifi to avoid false warnings on ethernet/VPN - Move _checkConnectivity inside try block so _downloading resets on exception - Use chatModelPreferenceStorageProvider instead of hardcoded key in reset logic - Close database via provider before deleting the file --- .../presentation/model_download_page.dart | 25 ++++++++-------- .../l10n/domain/locale_preference.dart | 2 +- .../settings/presentation/settings_page.dart | 29 +++++++++---------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/features/chat/presentation/model_download_page.dart b/lib/features/chat/presentation/model_download_page.dart index 5c00498..d68f2ea 100644 --- a/lib/features/chat/presentation/model_download_page.dart +++ b/lib/features/chat/presentation/model_download_page.dart @@ -23,7 +23,7 @@ class _ModelDownloadPageState extends ConsumerState { @override void initState() { super.initState(); - _startDownload(); + Future.microtask(_startDownload); } /// Returns true if the download should proceed, false otherwise. @@ -35,6 +35,7 @@ class _ModelDownloadPageState extends ConsumerState { final l10n = AppLocalizations.of(context); await showDialog( context: context, + barrierDismissible: false, builder: (context) => AlertDialog( title: Text(l10n.chatModelDownloadNoConnectionTitle), content: Text(l10n.chatModelDownloadNoConnectionBody), @@ -49,7 +50,8 @@ class _ModelDownloadPageState extends ConsumerState { return false; } - if (!results.contains(ConnectivityResult.wifi)) { + if (results.contains(ConnectivityResult.mobile) && + !results.contains(ConnectivityResult.wifi)) { if (!mounted) return false; final l10n = AppLocalizations.of(context); final proceed = await showDialog( @@ -83,18 +85,17 @@ class _ModelDownloadPageState extends ConsumerState { _progress = 0; }); - final shouldProceed = await _checkConnectivity(); - if (!shouldProceed) { - _downloading = false; - if (mounted) { - setState(() { - _error = 'connectivity'; - }); + try { + final shouldProceed = await _checkConnectivity(); + if (!shouldProceed) { + if (mounted) { + setState(() { + _error = 'connectivity'; + }); + } + return; } - return; - } - try { final model = await ref.read(chatModelPreferenceProvider.future); await FlutterGemma.installModel( diff --git a/lib/features/l10n/domain/locale_preference.dart b/lib/features/l10n/domain/locale_preference.dart index 3a50337..d8f6af3 100644 --- a/lib/features/l10n/domain/locale_preference.dart +++ b/lib/features/l10n/domain/locale_preference.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; sealed class LocalePreference { const LocalePreference(); - static const Set supportedLanguageCodes = {'en', 'fr', 'es', 'de'}; + static const Set supportedLanguageCodes = {'en', 'fr', 'es', 'de', 'it'}; String toStorageValue(); diff --git a/lib/features/settings/presentation/settings_page.dart b/lib/features/settings/presentation/settings_page.dart index cad87b1..437c626 100644 --- a/lib/features/settings/presentation/settings_page.dart +++ b/lib/features/settings/presentation/settings_page.dart @@ -7,8 +7,6 @@ import 'package:path/path.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite/sqflite.dart'; -import '../../chat/domain/chat_model_preference.dart'; - import '../../chat/presentation/backend_picker_tile.dart'; import '../../chat/presentation/expert_picker_tile.dart'; import '../../chat/presentation/model_picker_tile.dart'; @@ -179,28 +177,27 @@ class SettingsPage extends ConsumerWidget { ); if (confirmed != true) return; - // 1. Read installed model before clearing preferences. - final prefs = await SharedPreferences.getInstance(); - final installedModelName = prefs.getString('chat_model_installed'); + // 1. Read installed model via provider before clearing preferences. + final storage = + await ref.read(chatModelPreferenceStorageProvider.future); + final installedModel = storage.readInstalled(); // 2. Clear all shared preferences. + final prefs = await SharedPreferences.getInstance(); await prefs.clear(); // 3. Uninstall downloaded model if one exists. - if (installedModelName != null) { - for (final model in ChatModelPreference.values) { - if (model.name == installedModelName) { - try { - await FlutterGemma.uninstallModel(model.fileName); - } catch (_) { - // Best-effort — model file may already be gone. - } - break; - } + if (installedModel != null) { + try { + await FlutterGemma.uninstallModel(installedModel.fileName); + } catch (_) { + // Best-effort — model file may already be gone. } } - // 4. Delete SQLite database. + // 4. Close and delete SQLite database. + final db = await ref.read(chatDatabaseProvider.future); + await db.close(); final dbPath = join(await getDatabasesPath(), 'cookmate_chat.db'); await deleteDatabase(dbPath);