From eb55a0ba0a2730d877e644ac678773a769ddc1ee Mon Sep 17 00:00:00 2001 From: tsutsu3 Date: Thu, 19 Dec 2024 23:37:16 +0900 Subject: [PATCH] feat(ui): add opt-out screen on app startup (#57) --- __azurite_db_queue__.json | 1 + __azurite_db_queue_extent__.json | 1 + android/app/src/main/AndroidManifest.xml | 3 +- lib/base.dart | 2 +- lib/l10n/app_de.arb | 8 + lib/l10n/app_en.arb | 8 + lib/l10n/app_es.arb | 8 + lib/l10n/app_ja.arb | 8 + lib/l10n/app_pl.arb | 8 + lib/main.dart | 75 +++--- lib/models/repository/database.dart | 5 + lib/providers/app_config_provider.dart | 19 ++ lib/repository/database.dart | 11 +- lib/screens/settings/{ => about}/about.dart | 0 .../{ => about}/app_detail_modal.dart | 46 +--- .../settings/about/important_info_modal.dart | 80 ++++++ .../settings/{ => about}/legal_modal.dart | 1 + .../licenses_screen.dart} | 6 +- lib/screens/settings/about/privacy_modal.dart | 161 ++++++++++++ .../advanced_settings/advanced_options.dart | 8 +- .../app_lock/create_pass_code_modal.dart | 0 .../app_lock/remove_passcode_modal.dart | 0 .../app_unlock_setup_modal.dart | 4 +- .../enter_passcode_modal.dart | 0 .../advanced_settings/reset_modal.dart | 0 .../statistics_visualization_screen.dart | 0 .../auto_refresh_time_screen.dart | 0 .../{ => app_settings}/language_screen.dart | 0 .../logs_quantity_load_screen.dart | 0 .../{ => app_settings}/theme_screen.dart | 0 lib/screens/settings/settings.dart | 49 ++-- lib/widgets/start_warning_modal.dart | 234 +++++++++++++----- 32 files changed, 583 insertions(+), 163 deletions(-) create mode 100644 __azurite_db_queue__.json create mode 100644 __azurite_db_queue_extent__.json rename lib/screens/settings/{ => about}/about.dart (100%) rename lib/screens/settings/{ => about}/app_detail_modal.dart (76%) create mode 100644 lib/screens/settings/about/important_info_modal.dart rename lib/screens/settings/{ => about}/legal_modal.dart (98%) rename lib/screens/settings/{licenses.dart => about/licenses_screen.dart} (89%) create mode 100644 lib/screens/settings/about/privacy_modal.dart rename lib/screens/settings/{ => app_settings}/advanced_settings/advanced_options.dart (96%) rename lib/screens/settings/{ => app_settings}/advanced_settings/app_lock/create_pass_code_modal.dart (100%) rename lib/screens/settings/{ => app_settings}/advanced_settings/app_lock/remove_passcode_modal.dart (100%) rename lib/screens/settings/{ => app_settings}/advanced_settings/app_unlock_setup_modal.dart (98%) rename lib/screens/settings/{ => app_settings}/advanced_settings/enter_passcode_modal.dart (100%) rename lib/screens/settings/{ => app_settings}/advanced_settings/reset_modal.dart (100%) rename lib/screens/settings/{ => app_settings}/advanced_settings/statistics_visualization_screen.dart (100%) rename lib/screens/settings/{ => app_settings}/auto_refresh_time_screen.dart (100%) rename lib/screens/settings/{ => app_settings}/language_screen.dart (100%) rename lib/screens/settings/{ => app_settings}/logs_quantity_load_screen.dart (100%) rename lib/screens/settings/{ => app_settings}/theme_screen.dart (100%) diff --git a/__azurite_db_queue__.json b/__azurite_db_queue__.json new file mode 100644 index 00000000..b734f381 --- /dev/null +++ b/__azurite_db_queue__.json @@ -0,0 +1 @@ +{"filename":"c:\\Users\\ttm2t\\WorkSpace\\pi-hole-client\\__azurite_db_queue__.json","collections":[{"name":"$SERVICES_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{},"constraints":null,"uniqueNames":["accountName"],"transforms":{},"objType":"$SERVICES_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]},{"name":"$QUEUES_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{"accountName":{"name":"accountName","dirty":false,"values":[]},"name":{"name":"name","dirty":false,"values":[]}},"constraints":null,"uniqueNames":[],"transforms":{},"objType":"$QUEUES_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]},{"name":"$MESSAGES_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{"accountName":{"name":"accountName","dirty":false,"values":[]},"queueName":{"name":"queueName","dirty":false,"values":[]},"messageId":{"name":"messageId","dirty":false,"values":[]},"visibleTime":{"name":"visibleTime","dirty":false,"values":[]}},"constraints":null,"uniqueNames":[],"transforms":{},"objType":"$MESSAGES_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]}],"databaseVersion":1.5,"engineVersion":1.5,"autosave":true,"autosaveInterval":5000,"autosaveHandle":null,"throttledSaves":true,"options":{"persistenceMethod":"fs","autosave":true,"autosaveInterval":5000,"serializationMethod":"normal","destructureDelimiter":"$<\n"},"persistenceMethod":"fs","persistenceAdapter":null,"verbose":false,"events":{"init":[null],"loaded":[],"flushChanges":[],"close":[],"changes":[],"warning":[]},"ENV":"NODEJS"} \ No newline at end of file diff --git a/__azurite_db_queue_extent__.json b/__azurite_db_queue_extent__.json new file mode 100644 index 00000000..40e990d4 --- /dev/null +++ b/__azurite_db_queue_extent__.json @@ -0,0 +1 @@ +{"filename":"c:\\Users\\ttm2t\\WorkSpace\\pi-hole-client\\__azurite_db_queue_extent__.json","collections":[{"name":"$EXTENTS_COLLECTION$","data":[],"idIndex":null,"binaryIndices":{"id":{"name":"id","dirty":false,"values":[]}},"constraints":null,"uniqueNames":[],"transforms":{},"objType":"$EXTENTS_COLLECTION$","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":true,"ttl":null,"maxId":0,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]}],"databaseVersion":1.5,"engineVersion":1.5,"autosave":true,"autosaveInterval":5000,"autosaveHandle":null,"throttledSaves":true,"options":{"persistenceMethod":"fs","autosave":true,"autosaveInterval":5000,"serializationMethod":"normal","destructureDelimiter":"$<\n"},"persistenceMethod":"fs","persistenceAdapter":null,"verbose":false,"events":{"init":[null],"loaded":[],"flushChanges":[],"close":[],"changes":[],"warning":[]},"ENV":"NODEJS"} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c6d5e281..8a3b22e6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,8 @@ + android:icon="@mipmap/ic_launcher" + android:enableOnBackInvokedCallback="true"> with WidgetsBindingObserver { if (appConfigProvider.importantInfoReaden == false) { await showDialog( context: context, - builder: (BuildContext context) => const ImportantInfoModal()); + builder: (BuildContext context) => const StartInfoModal()); } }); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index ba83fa41..79a95599 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -15,11 +15,13 @@ "advancedSetup": "Erweiterte Einstellungen", "advancedSetupDescription": "Erweiterte Optionen", "advancedStatusFiltering": "Erweiterte Statusfilterung", + "agreePrivacyPolicy": "Durch Aktivieren dieser Option stimmen Sie unseren zu", "alias": "Alias", "all": "Alle", "allClientsSelected": "Alle Clients ausgewählt", "allItemsSelected": "Alle Elemente ausgewählt", "allStatusSelected": "Alle Status ausgewählt", + "allowCrashReport": "Absturzberichte senden erlauben?", "allowed": "Erlaubt", "alreadyBlacklist": "Domain existiert bereits in der Blacklist.", "alreadyWhitelist": "Domain existiert bereits in der Whitelist.", @@ -150,7 +152,10 @@ "fromOldestToLatest": "Alt nach Neu", "fromTime": "Von", "gettingPermission": "Berechtigungen werden geladen...", + "gettingStarted": "Erste Schritte", "gitHub": "Code auf GitHub ansehen", + "helpUsImprove": "Helfen Sie uns, besser zu werden", + "helpUsImproveMessage": "Um die Qualität der App zu verbessern, können wir bei Problemen anonyme Absturzberichte sammeln.\nDiese Berichte enthalten keine persönlich identifizierbaren Informationen.", "hideZeroValues": "Nullwerte ausblenden", "hideZeroValuesDescription": "Versteckt Nullwerte aus dem Client-Diagramm", "hits": "Treffer:", @@ -237,6 +242,8 @@ "pieChartDescription": "Zeigt die Daten in einem Tortendiagramm mit einer Legende.", "port": "Port", "portCannotEmpty": "Port Feld darf nicht leer sein", + "privacy": "Datenschutz", + "privacyInfo": "Datenschutz und Datenverwaltung", "privacyPolicy": "Datenschutzrichtlinie", "privacyPolicyDescription": "Datenschutzrichtlinie ansehen", "qrScanner": "QR-Code Scanner", @@ -332,6 +339,7 @@ "version": "Version", "versionDescription": "Wählen Sie die Version von Pi-hole, die Sie verwenden", "visitGooglePlay": "Google Play-Seite besuchen", + "welcomeToApp": "Willkommen in der App", "whitelist": "Zur Whitelist hinzufügen", "writeEmail": "Schreiben Sie mir per E-Mail.", "writeEmailDetails": "Die Email sollte im Optimalfall folgende Infos enthalten: Pi-hole und Web Interface Version, wie kann der Fehler reproduziert werden, Screenshots...\n\nFür alle Informationen sind wir sehr dankbar." diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5b9c9f08..660fc10a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -15,11 +15,13 @@ "advancedSetup": "Advanced settings", "advancedSetupDescription": "Advanced options", "advancedStatusFiltering": "Advanced status filtering", + "agreePrivacyPolicy": "By enabling this option, you agree to our", "alias": "Alias", "all": "All", "allClientsSelected": "All clients selected", "allItemsSelected": "All items selected", "allStatusSelected": "All status selected", + "allowCrashReport": "Allow crash report submission?", "allowed": "Allowed", "alreadyBlacklist": "Domain is already on blacklist.", "alreadyWhitelist": "Domain is already on whitelist.", @@ -150,7 +152,10 @@ "fromOldestToLatest": "From oldest to latest", "fromTime": "From time", "gettingPermission": "Getting permission...", + "gettingStarted": "Getting Started", "gitHub": "App code available on GitHub", + "helpUsImprove": "Help Us Improve", + "helpUsImproveMessage": "To improve app quality, we may collect anonymous crash reports when an issue occurs.\nThese reports do not contain any personally identifiable information. ", "hideZeroValues": "Hide zero values", "hideZeroValuesDescription": "Hides zero values from clients chart", "hits": "Hits:", @@ -237,6 +242,8 @@ "pieChartDescription": "Displays the data on a pie chart with the legend under it.", "port": "Port", "portCannotEmpty": "Port field cannot be empty", + "privacy": "Privacy", + "privacyInfo": "Privacy and Data Management", "privacyPolicy": "Privacy Policy", "privacyPolicyDescription": "View Privacy Policy", "qrScanner": "QR scanner", @@ -334,6 +341,7 @@ "version": "Version", "versionDescription": "Select the version of Pi-hole you are using", "visitGooglePlay": "Visit Google Play page", + "welcomeToApp": "Welcome to the App", "whitelist": "Add to whitelist", "writeEmail": "Write me by email.", "writeEmailDetails": "Include on the body of the email all the possible details: Pi-hole and web interface version, how to reproduce the issue, screenshots...\n\nAll provided information will be very welcome." diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 14d19ef6..c6679386 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -15,11 +15,13 @@ "advancedSetup": "Configuración avanzada", "advancedSetupDescription": "Opciones avanzadas", "advancedStatusFiltering": "Filtros de estado avanzados", + "agreePrivacyPolicy": "Al habilitar esta opción, aceptas nuestra", "alias": "Alias", "all": "Todos", "allClientsSelected": "Todos los clientes seleccionados", "allItemsSelected": "Todos los items seleccionados", "allStatusSelected": "Todos los estados seleccionados", + "allowCrashReport": "¿Permitir el envío de informes de fallos?", "allowed": "Permitidos", "alreadyBlacklist": "El dominio ya está en lista negra.", "alreadyWhitelist": "El dominio ya está en lista blanca.", @@ -152,7 +154,10 @@ "fromOldestToLatest": "De antiguo a nuevo", "fromTime": "Desde", "gettingPermission": "Obteniendo permiso...", + "gettingStarted": "Comenzando", "gitHub": "Código de la app disponible en GitHub", + "helpUsImprove": "Ayúdanos a mejorar", + "helpUsImproveMessage": "Para mejorar la calidad de la aplicación, podemos recopilar informes de fallos anónimos cuando ocurra un problema.\nEstos informes no contienen información personal identificable. ", "hideZeroValues": "Ocultar valores a cero", "hideZeroValuesDescription": "Oculta los valores a cero del gráfico de clientes", "hits": "Veces:", @@ -239,6 +244,8 @@ "pieChartDescription": "Muestra los datos en un gráfico circular con la leyenda debajo.", "port": "Puerto", "portCannotEmpty": "Puerto no puede estar vacío", + "privacy": "Privacidad", + "privacyInfo": "Privacidad y gestión de datos", "privacyPolicy": "Política de Privacidad", "privacyPolicyDescription": "Ver Política de Privacidad", "qrScanner": "Escáner QR", @@ -336,6 +343,7 @@ "version": "Versión", "versionDescription": "Seleccione la versión de Pi-hole que está utilizando", "visitGooglePlay": "Visita la página de Google Play", + "welcomeToApp": "Bienvenido a la aplicación", "whitelist": "Añadir a lista blanca", "writeEmail": "Escríbeme por correo electrónico.", "writeEmailDetails": "Incluye en el cuerpo del correo todos los detalles posibles: Pi-hole y versión de la interfaz web, cómo reproducir el problema, capturas de pantalla...\n\nToda la información proporcionada será muy bienvenida." diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index cf9e67e0..ad4ec1ee 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -15,11 +15,13 @@ "advancedSetup": "高度な設定", "advancedSetupDescription": "高度なオプション", "advancedStatusFiltering": "高度なステータスフィルタリング", + "agreePrivacyPolicy": "このオプションを有効にすると、次の内容に同意したものとみなされます", "alias": "エイリアス", "all": "すべて", "allClientsSelected": "すべてのクライアントが選択されました", "allItemsSelected": "全ての項目が選択されました", "allStatusSelected": "全ステータスが選択されました", + "allowCrashReport": "クラッシュレポートの送信を許可しますか?", "allowed": "許可済み", "alreadyBlacklist": "ドメインは既にブラックリストにあります。", "alreadyWhitelist": "ドメインは既にホワイトリストにあります。", @@ -150,7 +152,10 @@ "fromOldestToLatest": "古い順", "fromTime": "開始時刻", "gettingPermission": "権限を取得中...", + "gettingStarted": "はじめに", "gitHub": "アプリコードはGitHubで利用可能", + "helpUsImprove": "改善へのご協力をお願いします", + "helpUsImproveMessage": "アプリの品質向上のため、問題が発生した際に匿名のクラッシュレポートを収集することがあります。\nこれらのレポートには個人を特定できる情報は含まれません。クラッシュレポートの送信を許可しますか?", "hideZeroValues": "ゼロ値を非表示", "hideZeroValuesDescription": "クライアントチャートからゼロ値を非表示にします", "hits": "ヒット:", @@ -237,6 +242,8 @@ "pieChartDescription": "円グラフでデータを表示し、その下に凡例を表示します。", "port": "ポート", "portCannotEmpty": "ポートフィールドを空にすることはできません", + "privacy": "プライバシー", + "privacyInfo": "プライバシーとデータ管理", "privacyPolicy": "プライバシーポリシー", "privacyPolicyDescription": "プライバシーポリシーを見る", "qrScanner": "QRスキャナー", @@ -334,6 +341,7 @@ "version": "バージョン", "versionDescription": "使用している Pi-hole のバージョンを選択してください", "visitGooglePlay": "Google Playページを訪問", + "welcomeToApp": "アプリへようこそ", "whitelist": "ホワイトリストに追加", "writeEmail": "メールで私に書いてください。", "writeEmailDetails": "メール本文には可能な限り詳細を記載してください:Pi-holeおよびWebインターフェースのバージョン、問題の再現方法、スクリーンショットなど。\n\n提供される情報はすべて歓迎します。" diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index d6507612..55e4a00f 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -15,11 +15,13 @@ "advancedSetup": "Ustawienia zaawansowane", "advancedSetupDescription": "Wykresy, bezpieczenstwo i inne", "advancedStatusFiltering": "Zaawansowane filtrowanie stanów", + "agreePrivacyPolicy": "Włączając tę opcję, zgadzasz się na nasze", "alias": "Alias", "all": "Wszystko", "allClientsSelected": "Wybrano wszystkich klientów", "allItemsSelected": "Wybrano wszystkie elementy", "allStatusSelected": "Wybrano wszystkie stany", + "allowCrashReport": "Zezwolić na przesyłanie raportów o awariach?", "allowed": "Dozwolone", "alreadyBlacklist": "Domena jest już na czarnej liście.", "alreadyWhitelist": "Domena jest już na białej liście.", @@ -148,7 +150,10 @@ "fromOldestToLatest": "Od najstarszych do najnowszych", "fromTime": "Od kiedy", "gettingPermission": "Uzyskiwanie pozwolenia...", + "gettingStarted": "Pierwsze kroki", "gitHub": "Kod aplikacji dostępny na GitHubie", + "helpUsImprove": "Pomóż nam ulepszyć", + "helpUsImproveMessage": "Aby poprawić jakość aplikacji, możemy zbierać anonimowe raporty o awariach w przypadku wystąpienia problemu.\nRaporty te nie zawierają żadnych danych osobowych. ", "hideZeroValues": "Ukryj wartości zerowe", "hideZeroValuesDescription": "Ukrywa wartości zerowe na wykresie klientów", "hits": "Trafienia:", @@ -235,6 +240,8 @@ "pieChartDescription": "Wyświetla dane na wykresie kołowym z legendą pod nimi.", "port": "Port", "portCannotEmpty": "Pole portu nie może być puste", + "privacy": "Prywatność", + "privacyInfo": "Prywatność i zarządzanie danymi", "privacyPolicy": "Polityka Prywatności", "privacyPolicyDescription": "Zobacz Politykę Prywatności", "qrScanner": "Skaner QR", @@ -330,6 +337,7 @@ "version": "Wersja", "versionDescription": "Wybierz wersję Pi-hole, której używasz", "visitGooglePlay": "Odwiedź stronę Google Play", + "welcomeToApp": "Witaj w aplikacji", "whitelist": "Dodaj do białej listy", "writeEmail": "Napisz do mnie na e-mail.", "writeEmailDetails": "W treści e-maila umieść wszystkie możliwe szczegóły: wersję Pi-hole i interfejsu sieciowego, sposób odtworzenia problemu, zrzuty ekranu...\n\nWszystkie dostarczone informacje będą bardzo mile widziane." diff --git a/lib/main.dart b/lib/main.dart index 6470cd3a..16ecfcd7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_app_lock/flutter_app_lock.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:local_auth/local_auth.dart'; +import 'package:pi_hole_client/functions/logger.dart'; import 'package:pi_hole_client/repository/secure_storage.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; @@ -114,6 +115,47 @@ void main() async { configProvider.setIosInfo(iosInfo); } + Future initializeSentry() async { + if (configProvider.sendCrashReports == false) { + logger.d("Send Crash Reports: OFF"); + await Sentry.close(); + return; + } + + if ((kReleaseMode && + (dotenv.env['SENTRY_DSN'] != null && + dotenv.env['SENTRY_DSN'] != "")) || + (dotenv.env['ENABLE_SENTRY'] == "true" && + (dotenv.env['SENTRY_DSN'] != null && + dotenv.env['SENTRY_DSN'] != ""))) { + logger.d("Send Crash Reports: ON"); + SentryFlutter.init( + (options) { + options.dsn = dotenv.env['SENTRY_DSN']; + options.sendDefaultPii = false; + options.attachScreenshot = + dotenv.env['ENABLE_SENTRY_SCREENSHOTS'] == "true"; + options.beforeSend = (event, hint) { + if (event.throwable is HttpException) { + return null; + } + + if (event.message?.formatted.contains("Unexpected character") ?? + false || + (event.throwable != null && + event.throwable! + .toString() + .contains("Unexpected character"))) { + return null; // Exclude this event + } + + return event; + }; + }, + ); + } + } + void startApp() => runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: ((context) => serversProvider)), @@ -142,37 +184,8 @@ void main() async { ), )); - if ((kReleaseMode && - (dotenv.env['SENTRY_DSN'] != null && - dotenv.env['SENTRY_DSN'] != "")) || - (dotenv.env['ENABLE_SENTRY'] == "true" && - (dotenv.env['SENTRY_DSN'] != null && - dotenv.env['SENTRY_DSN'] != ""))) { - SentryFlutter.init((options) { - options.dsn = dotenv.env['SENTRY_DSN']; - options.sendDefaultPii = false; - options.attachScreenshot = - dotenv.env['ENABLE_SENTRY_SCREENSHOTS'] == "true"; - options.beforeSend = (event, hint) { - if (event.throwable is HttpException) { - return null; - } - - if (event.message?.formatted.contains("Unexpected character") ?? - false || - (event.throwable != null && - event.throwable! - .toString() - .contains("Unexpected character"))) { - return null; // Exclude this event - } - - return event; - }; - }, appRunner: () => startApp()); - } else { - startApp(); - } + initializeSentry(); + startApp(); } Future loadAppInfo() async { diff --git a/lib/models/repository/database.dart b/lib/models/repository/database.dart index 4caa844d..1e2bec32 100644 --- a/lib/models/repository/database.dart +++ b/lib/models/repository/database.dart @@ -100,6 +100,7 @@ class AppDbData { final int importantInfoReaden; final int hideZeroValues; final int statisticsVisualizationMode; + final int sendCrashReports; AppDbData({ required this.autoRefreshTime, @@ -114,6 +115,7 @@ class AppDbData { required this.importantInfoReaden, required this.hideZeroValues, required this.statisticsVisualizationMode, + required this.sendCrashReports, }); factory AppDbData.fromMap(Map map) { @@ -130,6 +132,7 @@ class AppDbData { importantInfoReaden: map['importantInfoReaden'] as int, hideZeroValues: map['hideZeroValues'] as int, statisticsVisualizationMode: map['statisticsVisualizationMode'] as int, + sendCrashReports: map['sendCrashReports'] as int, ); } @@ -150,6 +153,7 @@ class AppDbData { importantInfoReaden: instance.importantInfoReaden, hideZeroValues: instance.hideZeroValues, statisticsVisualizationMode: instance.statisticsVisualizationMode, + sendCrashReports: instance.sendCrashReports, ); } @@ -167,6 +171,7 @@ class AppDbData { "importantInfoReaden": importantInfoReaden, "hideZeroValues": hideZeroValues, "statisticsVisualizationMode": statisticsVisualizationMode, + "sendCrashReports": sendCrashReports, }; } } diff --git a/lib/providers/app_config_provider.dart b/lib/providers/app_config_provider.dart index 2b9cac88..e35e4643 100644 --- a/lib/providers/app_config_provider.dart +++ b/lib/providers/app_config_provider.dart @@ -27,6 +27,7 @@ class AppConfigProvider with ChangeNotifier { int _importantInfoReaden = 0; int _hideZeroValues = 0; int _statisticsVisualizationMode = 0; + int _sendCrashReports = 0; int? _selectedSettingsScreen; String _selectedLanguage = SchedulerBinding.instance.platformDispatcher.locale.languageCode; @@ -144,6 +145,10 @@ class AppConfigProvider with ChangeNotifier { return _statisticsVisualizationMode; } + bool get sendCrashReports { + return _sendCrashReports == 0 ? false : true; + } + List get logs { return _logs; } @@ -283,6 +288,18 @@ class AppConfigProvider with ChangeNotifier { } } + Future setSendCrashReports(bool status) async { + final updated = await _repository.updateConfigQuery( + column: 'sendCrashReports', value: status == true ? 1 : 0); + if (updated == true) { + _sendCrashReports = status == true ? 1 : 0; + notifyListeners(); + return true; + } else { + return false; + } + } + void saveFromDb(AppDbData dbData) { _autoRefreshTime = dbData.autoRefreshTime; _selectedTheme = dbData.theme; @@ -296,6 +313,7 @@ class AppConfigProvider with ChangeNotifier { _importantInfoReaden = dbData.importantInfoReaden; _hideZeroValues = dbData.hideZeroValues; _statisticsVisualizationMode = dbData.statisticsVisualizationMode; + _sendCrashReports = dbData.sendCrashReports; if (dbData.passCode != null) { _appUnlocked = false; @@ -404,6 +422,7 @@ class AppConfigProvider with ChangeNotifier { _importantInfoReaden = 0; _hideZeroValues = 0; _statisticsVisualizationMode = 0; + _sendCrashReports = 0; notifyListeners(); diff --git a/lib/repository/database.dart b/lib/repository/database.dart index 8ce5b403..bdd229c2 100644 --- a/lib/repository/database.dart +++ b/lib/repository/database.dart @@ -100,7 +100,8 @@ class DatabaseRepository { useBiometricAuth NUMERIC NOT NULL, importantInfoReaden NUMERIC NOT NULL, hideZeroValues NUMERIC NOT NULL, - statisticsVisualizationMode NUMERIC NOT NULL + statisticsVisualizationMode NUMERIC NOT NULL, + sendCrashReports NUMERIC NOT NULL ) """); await db.execute(""" @@ -115,8 +116,9 @@ class DatabaseRepository { useBiometricAuth, importantInfoReaden, hideZeroValues, - statisticsVisualizationMode - ) VALUES (5, 0, 'en', 0, 0, 0, 2, 0, 0, 0, 0) + statisticsVisualizationMode, + sendCrashReports + ) VALUES (5, 0, 'en', 0, 0, 0, 2, 0, 0, 0, 0, 0) """); }, onUpgrade: (Database db, int oldVersion, int newVersion) async {}, @@ -486,7 +488,8 @@ class DatabaseRepository { 'oneColumnLegend': 0, 'reducedDataCharts': 0, 'logsPerQuery': 2, - 'useBiometricAuth': 0 + 'useBiometricAuth': 0, + 'sendCrashReports': 0, }, ); return true; diff --git a/lib/screens/settings/about.dart b/lib/screens/settings/about/about.dart similarity index 100% rename from lib/screens/settings/about.dart rename to lib/screens/settings/about/about.dart diff --git a/lib/screens/settings/app_detail_modal.dart b/lib/screens/settings/about/app_detail_modal.dart similarity index 76% rename from lib/screens/settings/app_detail_modal.dart rename to lib/screens/settings/about/app_detail_modal.dart index 0462f989..91aae740 100644 --- a/lib/screens/settings/app_detail_modal.dart +++ b/lib/screens/settings/about/app_detail_modal.dart @@ -26,7 +26,6 @@ class _AppDetailModalState extends State { return AlertDialog( scrollable: true, - contentPadding: const EdgeInsets.all(0), title: Column( children: [ Icon( @@ -43,6 +42,7 @@ class _AppDetailModalState extends State { ), ], ), + contentPadding: EdgeInsets.all(40), content: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -67,40 +67,20 @@ class _AppDetailModalState extends State { subtitle: AppLocalizations.of(context)!.supportFormDescription, colorScheme: colorScheme, ), + Padding(padding: const EdgeInsets.symmetric(vertical: 20)), _appDetailLine( - url: privacyPolicyUrl, - icon: Icon(Icons.security_rounded, color: colorScheme.onSurface), - title: AppLocalizations.of(context)!.privacyPolicy, - subtitle: AppLocalizations.of(context)!.privacyPolicyDescription, + title: AppLocalizations.of(context)!.appVersion, + subtitle: _appVersion, colorScheme: colorScheme, + showWebViewIcon: false, ), - Padding(padding: const EdgeInsets.symmetric(vertical: 10)), - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: _appDetailLine( - title: AppLocalizations.of(context)!.appVersion, - subtitle: _appVersion, - colorScheme: colorScheme, - showWebViewIcon: false, - ), - ), - Expanded( - child: _appDetailLine( - title: AppLocalizations.of(context)!.createdBy, - subtitle: "tsutsu3", - colorScheme: colorScheme, - showWebViewIcon: false, - ), - ), - ], - ), - ], + _appDetailLine( + title: AppLocalizations.of(context)!.createdBy, + subtitle: "tsutsu3", + colorScheme: colorScheme, + showWebViewIcon: false, ), - Padding(padding: const EdgeInsets.symmetric(vertical: 10)), + Padding(padding: const EdgeInsets.only(top: 10)), ], ), actions: [ @@ -125,11 +105,11 @@ class _AppDetailModalState extends State { child: InkWell( onTap: url != null ? () => openUrl(url) : null, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 10), child: Row( children: [ icon ?? const SizedBox(width: 0), - const SizedBox(width: 20), + if (icon != null) const SizedBox(width: 20), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/screens/settings/about/important_info_modal.dart b/lib/screens/settings/about/important_info_modal.dart new file mode 100644 index 00000000..b5b88b9b --- /dev/null +++ b/lib/screens/settings/about/important_info_modal.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class ImportantInfoModal extends StatelessWidget { + const ImportantInfoModal({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + scrollable: true, + title: Column( + children: [ + Icon( + Icons.info_rounded, + size: 24, + color: Theme.of(context).colorScheme.secondary, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.importantAnnouncement, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, color: Theme.of(context).colorScheme.onSurface), + ), + ), + ], + ), + contentPadding: EdgeInsets.all(40), + content: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.requiredVersions, + textAlign: TextAlign.center, + style: + const TextStyle(fontWeight: FontWeight.w500, fontSize: 16), + ), + Container( + width: double.maxFinite, + margin: const EdgeInsets.only(top: 10), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("Pi-hole: v5.12+"), + SizedBox(height: 5), + Text("Web interface: v5.14.2+") + ], + ), + ), + const SizedBox(height: 10), + Text( + AppLocalizations.of(context)!.olderVersion, + textAlign: TextAlign.justify, + ) + ], + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.letMeKnow, + textAlign: TextAlign.justify, + ), + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.howToContact, + textAlign: TextAlign.justify, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close), + ) + ], + ); + } +} diff --git a/lib/screens/settings/legal_modal.dart b/lib/screens/settings/about/legal_modal.dart similarity index 98% rename from lib/screens/settings/legal_modal.dart rename to lib/screens/settings/about/legal_modal.dart index d988644d..da2801df 100644 --- a/lib/screens/settings/legal_modal.dart +++ b/lib/screens/settings/about/legal_modal.dart @@ -31,6 +31,7 @@ class LegalModal extends StatelessWidget { ), ], ), + contentPadding: EdgeInsets.all(40), content: SingleChildScrollView( child: Text(noticeText), ), diff --git a/lib/screens/settings/licenses.dart b/lib/screens/settings/about/licenses_screen.dart similarity index 89% rename from lib/screens/settings/licenses.dart rename to lib/screens/settings/about/licenses_screen.dart index 5c626e69..69151226 100644 --- a/lib/screens/settings/licenses.dart +++ b/lib/screens/settings/about/licenses_screen.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart' hide LicensePage; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:pi_hole_client/screens/settings/about.dart'; +import 'package:pi_hole_client/screens/settings/about/about.dart'; -class License extends StatelessWidget { - const License({super.key}); +class LicensesScreen extends StatelessWidget { + const LicensesScreen({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/screens/settings/about/privacy_modal.dart b/lib/screens/settings/about/privacy_modal.dart new file mode 100644 index 00000000..a2147532 --- /dev/null +++ b/lib/screens/settings/about/privacy_modal.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:expandable/expandable.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:pi_hole_client/constants/urls.dart'; +import 'package:pi_hole_client/functions/open_url.dart'; +import 'package:pi_hole_client/providers/app_config_provider.dart'; +import 'package:provider/provider.dart'; + +class PrivacyModal extends StatefulWidget { + const PrivacyModal({super.key}); + + @override + State createState() => _PrivacyModalState(); +} + +class _PrivacyModalState extends State { + final expandableController = ExpandableController(); + + Future _getSendCrashReports(AppConfigProvider appConfigProvider) async { + return appConfigProvider.sendCrashReports; + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final appConfigProvider = + Provider.of(context, listen: true); + + return FutureBuilder( + future: _getSendCrashReports(appConfigProvider), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Dialog( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text('Error: ${snapshot.error}'), + ), + ); + } + + final sendCrashReportStatus = snapshot.data ?? false; + + return AlertDialog( + scrollable: true, + title: Column( + children: [ + Icon( + Icons.contact_page_rounded, + size: 24, + color: colorScheme.secondary, + ), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 20, horizontal: 20), + child: Text( + AppLocalizations.of(context)!.privacyInfo, + style: const TextStyle(fontSize: 24), + textAlign: TextAlign.center, + ), + ), + ], + ), + contentPadding: EdgeInsets.all(40), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _appDetailLine( + url: privacyPolicyUrl, + icon: Icon(Icons.privacy_tip_rounded, + color: colorScheme.onSurface), + title: AppLocalizations.of(context)!.privacyPolicy, + subtitle: + AppLocalizations.of(context)!.privacyPolicyDescription, + colorScheme: colorScheme, + ), + Padding(padding: const EdgeInsets.only(top: 40)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + AppLocalizations.of(context)!.allowCrashReport, + textAlign: TextAlign.left, + ), + ), + Switch( + value: sendCrashReportStatus, + onChanged: (value) async { + await appConfigProvider.setSendCrashReports(value); + }, + ), + ], + ), + const Padding(padding: EdgeInsets.only(top: 10)), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close), + ), + ], + ); + }, + ); + } + + Widget _appDetailLine({ + String? url, + Widget? icon, + required String title, + required String subtitle, + required ColorScheme colorScheme, + bool showWebViewIcon = true, + }) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: url != null ? () => openUrl(url) : null, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + icon ?? const SizedBox(width: 0), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16, + color: colorScheme.onSurface, + ), + ), + const SizedBox(height: 5), + Text( + subtitle, + style: TextStyle(color: colorScheme.onSurfaceVariant), + ), + ], + ), + ), + if (showWebViewIcon) + Tooltip( + message: AppLocalizations.of(context)!.openExternalUrl, + child: Icon( + Icons.open_in_browser_rounded, + size: 20, + color: colorScheme.onSurfaceVariant.withOpacity(0.6), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/settings/advanced_settings/advanced_options.dart b/lib/screens/settings/app_settings/advanced_settings/advanced_options.dart similarity index 96% rename from lib/screens/settings/advanced_settings/advanced_options.dart rename to lib/screens/settings/app_settings/advanced_settings/advanced_options.dart index 4cd9f760..1813b7de 100644 --- a/lib/screens/settings/advanced_settings/advanced_options.dart +++ b/lib/screens/settings/app_settings/advanced_settings/advanced_options.dart @@ -7,11 +7,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:pi_hole_client/screens/app_logs/app_logs.dart'; import 'package:pi_hole_client/widgets/section_label.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/reset_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/reset_modal.dart'; import 'package:pi_hole_client/widgets/custom_list_tile.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/app_unlock_setup_modal.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/enter_passcode_modal.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/statistics_visualization_screen.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/app_unlock_setup_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/enter_passcode_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/statistics_visualization_screen.dart'; import 'package:pi_hole_client/config/system_overlay_style.dart'; import 'package:pi_hole_client/functions/snackbar.dart'; diff --git a/lib/screens/settings/advanced_settings/app_lock/create_pass_code_modal.dart b/lib/screens/settings/app_settings/advanced_settings/app_lock/create_pass_code_modal.dart similarity index 100% rename from lib/screens/settings/advanced_settings/app_lock/create_pass_code_modal.dart rename to lib/screens/settings/app_settings/advanced_settings/app_lock/create_pass_code_modal.dart diff --git a/lib/screens/settings/advanced_settings/app_lock/remove_passcode_modal.dart b/lib/screens/settings/app_settings/advanced_settings/app_lock/remove_passcode_modal.dart similarity index 100% rename from lib/screens/settings/advanced_settings/app_lock/remove_passcode_modal.dart rename to lib/screens/settings/app_settings/advanced_settings/app_lock/remove_passcode_modal.dart diff --git a/lib/screens/settings/advanced_settings/app_unlock_setup_modal.dart b/lib/screens/settings/app_settings/advanced_settings/app_unlock_setup_modal.dart similarity index 98% rename from lib/screens/settings/advanced_settings/app_unlock_setup_modal.dart rename to lib/screens/settings/app_settings/advanced_settings/app_unlock_setup_modal.dart index c83bd536..dce68114 100644 --- a/lib/screens/settings/advanced_settings/app_unlock_setup_modal.dart +++ b/lib/screens/settings/app_settings/advanced_settings/app_unlock_setup_modal.dart @@ -5,8 +5,8 @@ import 'package:local_auth/local_auth.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/app_lock/remove_passcode_modal.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/app_lock/create_pass_code_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/app_lock/remove_passcode_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/app_lock/create_pass_code_modal.dart'; import 'package:pi_hole_client/providers/app_config_provider.dart'; import 'package:pi_hole_client/functions/snackbar.dart'; diff --git a/lib/screens/settings/advanced_settings/enter_passcode_modal.dart b/lib/screens/settings/app_settings/advanced_settings/enter_passcode_modal.dart similarity index 100% rename from lib/screens/settings/advanced_settings/enter_passcode_modal.dart rename to lib/screens/settings/app_settings/advanced_settings/enter_passcode_modal.dart diff --git a/lib/screens/settings/advanced_settings/reset_modal.dart b/lib/screens/settings/app_settings/advanced_settings/reset_modal.dart similarity index 100% rename from lib/screens/settings/advanced_settings/reset_modal.dart rename to lib/screens/settings/app_settings/advanced_settings/reset_modal.dart diff --git a/lib/screens/settings/advanced_settings/statistics_visualization_screen.dart b/lib/screens/settings/app_settings/advanced_settings/statistics_visualization_screen.dart similarity index 100% rename from lib/screens/settings/advanced_settings/statistics_visualization_screen.dart rename to lib/screens/settings/app_settings/advanced_settings/statistics_visualization_screen.dart diff --git a/lib/screens/settings/auto_refresh_time_screen.dart b/lib/screens/settings/app_settings/auto_refresh_time_screen.dart similarity index 100% rename from lib/screens/settings/auto_refresh_time_screen.dart rename to lib/screens/settings/app_settings/auto_refresh_time_screen.dart diff --git a/lib/screens/settings/language_screen.dart b/lib/screens/settings/app_settings/language_screen.dart similarity index 100% rename from lib/screens/settings/language_screen.dart rename to lib/screens/settings/app_settings/language_screen.dart diff --git a/lib/screens/settings/logs_quantity_load_screen.dart b/lib/screens/settings/app_settings/logs_quantity_load_screen.dart similarity index 100% rename from lib/screens/settings/logs_quantity_load_screen.dart rename to lib/screens/settings/app_settings/logs_quantity_load_screen.dart diff --git a/lib/screens/settings/theme_screen.dart b/lib/screens/settings/app_settings/theme_screen.dart similarity index 100% rename from lib/screens/settings/theme_screen.dart rename to lib/screens/settings/app_settings/theme_screen.dart diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart index a9ca66af..33324004 100644 --- a/lib/screens/settings/settings.dart +++ b/lib/screens/settings/settings.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:pi_hole_client/constants/languages.dart'; -import 'package:pi_hole_client/screens/settings/language_screen.dart'; -import 'package:pi_hole_client/screens/settings/licenses.dart'; +import 'package:pi_hole_client/screens/settings/about/important_info_modal.dart'; +import 'package:pi_hole_client/screens/settings/about/privacy_modal.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/language_screen.dart'; +import 'package:pi_hole_client/screens/settings/about/licenses_screen.dart'; import 'package:provider/provider.dart'; import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:pi_hole_client/screens/settings/logs_quantity_load_screen.dart'; -import 'package:pi_hole_client/screens/settings/advanced_settings/advanced_options.dart'; -import 'package:pi_hole_client/screens/settings/theme_screen.dart'; -import 'package:pi_hole_client/screens/settings/auto_refresh_time_screen.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/logs_quantity_load_screen.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/advanced_settings/advanced_options.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/theme_screen.dart'; +import 'package:pi_hole_client/screens/settings/app_settings/auto_refresh_time_screen.dart'; import 'package:pi_hole_client/screens/servers/servers.dart'; -import 'package:pi_hole_client/screens/settings/app_detail_modal.dart'; -import 'package:pi_hole_client/widgets/start_warning_modal.dart'; +import 'package:pi_hole_client/screens/settings/about/app_detail_modal.dart'; import 'package:pi_hole_client/widgets/custom_list_tile.dart'; import 'package:pi_hole_client/widgets/section_label.dart'; -import 'package:pi_hole_client/screens/settings/legal_modal.dart'; +import 'package:pi_hole_client/screens/settings/about/legal_modal.dart'; import 'package:pi_hole_client/config/urls.dart'; import 'package:pi_hole_client/functions/open_url.dart'; @@ -128,6 +129,13 @@ class _SettingsWidgetState extends State { builder: (context) => AppDetailModal(appVersion: appVersion)); } + void openPrivacyModal() { + showDialog( + context: context, + builder: (context) => const PrivacyModal(), + ); + } + String getThemeString() { switch (appConfigProvider.selectedThemeNumber) { case 0: @@ -237,21 +245,26 @@ class _SettingsWidgetState extends State { description: AppLocalizations.of(context)!.aboutThisApp, onTap: openAppDetailModal, ), - settingsTile( - title: AppLocalizations.of(context)!.licenses, - subtitle: AppLocalizations.of(context)!.licensesInfo, - screenToNavigate: const License(), - thisItem: 6, - ), CustomListTile( - label: AppLocalizations.of(context)!.legal, - description: AppLocalizations.of(context)!.legalInfo, - onTap: openLegalModal), + label: AppLocalizations.of(context)!.privacy, + description: + AppLocalizations.of(context)!.privacyInfo, + onTap: openPrivacyModal), CustomListTile( label: AppLocalizations.of(context)! .importantInformation, description: AppLocalizations.of(context)!.readIssues, onTap: openImportantInformationModal), + CustomListTile( + label: AppLocalizations.of(context)!.legal, + description: AppLocalizations.of(context)!.legalInfo, + onTap: openLegalModal), + settingsTile( + title: AppLocalizations.of(context)!.licenses, + subtitle: AppLocalizations.of(context)!.licensesInfo, + screenToNavigate: const LicensesScreen(), + thisItem: 6, + ), Padding( padding: const EdgeInsets.all(15), child: Row( diff --git a/lib/widgets/start_warning_modal.dart b/lib/widgets/start_warning_modal.dart index 0325e20c..6d3fb27c 100644 --- a/lib/widgets/start_warning_modal.dart +++ b/lib/widgets/start_warning_modal.dart @@ -1,89 +1,191 @@ // ignore_for_file: use_build_context_synchronously +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:pi_hole_client/constants/urls.dart'; +import 'package:pi_hole_client/functions/open_url.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:pi_hole_client/providers/app_config_provider.dart'; -class ImportantInfoModal extends StatelessWidget { - const ImportantInfoModal({super.key}); +class StartInfoModal extends StatelessWidget { + const StartInfoModal({super.key}); + + Future getSendCrashReports(AppConfigProvider appConfigProvider) async { + return appConfigProvider.sendCrashReports; + } @override Widget build(BuildContext context) { final appConfigProvider = - Provider.of(context, listen: false); + Provider.of(context, listen: true); - return AlertDialog( - scrollable: true, - title: Column( - children: [ - Icon( - Icons.info_rounded, - size: 24, - color: Theme.of(context).colorScheme.secondary, - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: Text( - AppLocalizations.of(context)!.importantAnnouncement, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, color: Theme.of(context).colorScheme.onSurface), + return FutureBuilder( + future: getSendCrashReports(appConfigProvider), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Dialog( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text('Error: ${snapshot.error}'), ), - ), - ], - ), - content: Column( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - AppLocalizations.of(context)!.requiredVersions, - textAlign: TextAlign.center, - style: - const TextStyle(fontWeight: FontWeight.w500, fontSize: 16), - ), - Container( - width: double.maxFinite, - margin: const EdgeInsets.only(top: 10), - child: const Column( + ); + } + + final sendCrashReportStatus = snapshot.data ?? false; + + return Dialog( + child: Container( + constraints: const BoxConstraints( + maxWidth: 800, + ), + child: IntrinsicHeight( + child: AlertDialog( + scrollable: true, + title: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Pi-hole: v5.12+"), - SizedBox(height: 5), - Text("Web interface: v5.14.2+") + Icon( + Icons.info_rounded, + size: 24, + color: Theme.of(context).colorScheme.secondary, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.gettingStarted, + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ), + contentPadding: EdgeInsets.only(top: 60), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Show the required versions + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.requiredVersions, + textAlign: TextAlign.left, + style: const TextStyle( + fontWeight: FontWeight.w500, fontSize: 16), + ), + Container( + width: double.maxFinite, + margin: const EdgeInsets.only(top: 10), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Pi-hole: v5.12+", + textAlign: TextAlign.left, + ), + SizedBox(height: 5), + Text("Web interface: v5.14.2+", + textAlign: TextAlign.left), + ], + ), + ), + const Padding(padding: EdgeInsets.only(top: 10)), + Text( + AppLocalizations.of(context)!.olderVersion, + textAlign: TextAlign.left, + ), + ], + ), + const Padding(padding: EdgeInsets.only(top: 60)), + // Help improve the app + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)!.helpUsImprove, + textAlign: TextAlign.left, + style: const TextStyle( + fontWeight: FontWeight.w500, fontSize: 16), + ), + const Padding(padding: EdgeInsets.only(top: 10)), + Text( + AppLocalizations.of(context)!.helpUsImproveMessage, + textAlign: TextAlign.left, + ), + const Padding(padding: EdgeInsets.only(top: 20)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Text( + AppLocalizations.of(context)!.allowCrashReport, + textAlign: TextAlign.left, + style: const TextStyle( + fontWeight: FontWeight.w500), + ), + ), + Switch( + value: sendCrashReportStatus, + onChanged: (value) async { + await appConfigProvider + .setSendCrashReports(value); + }, + ), + ], + ), + RichText( + text: TextSpan( + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 16, + ), + children: [ + TextSpan( + text: AppLocalizations.of(context)! + .agreePrivacyPolicy, + style: const TextStyle(fontSize: 14), + ), + TextSpan(text: ' '), + TextSpan( + text: + AppLocalizations.of(context)!.privacyPolicy, + style: const TextStyle( + color: + Colors.blue, // ToDo: Use the theme color + decoration: TextDecoration.underline, + fontSize: 14, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => openUrl(privacyPolicyUrl), + ), + ], + ), + ), + ], + ), + const Padding(padding: EdgeInsets.only(top: 20)), ], ), + actionsPadding: EdgeInsets.only(right: 0, bottom: 20), + actions: [ + TextButton( + onPressed: () async { + await appConfigProvider.setImportantInfoReaden(true); + Navigator.pop(context); + }, + child: Text(AppLocalizations.of(context)!.close), + ), + ], ), - const SizedBox(height: 10), - Text( - AppLocalizations.of(context)!.olderVersion, - textAlign: TextAlign.justify, - ) - ], - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.letMeKnow, - textAlign: TextAlign.justify, - ), - const SizedBox(height: 30), - Text( - AppLocalizations.of(context)!.howToContact, - textAlign: TextAlign.justify, + ), ), - ], - ), - actions: [ - TextButton( - onPressed: () async { - await appConfigProvider.setImportantInfoReaden(true); - Navigator.pop(context); - }, - child: Text(AppLocalizations.of(context)!.close)) - ], + ); + }, ); } }