diff --git a/.gitignore b/.gitignore index ddef709..e12bca4 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ app.*.map.json /android/fastlane/build/outputs/ /android/fastlane/report.xml /lib/version.dart +/.env diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9174819..c565d3c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -480,5 +480,24 @@ "type": "int" } } - } + }, + "@_SURVEY": {}, + "survey_card_title": "Nutzerumfrage", + "survey_card_subtitle": "Studienarbeit MoveTopia", + "survey_card_description": "Wir möchten gerne deine Meinung zu MoveTopia erfahren. Deine Teilnahme hilft uns, die App zu verbessern.", + "survey_card_remind_later": "Später erinnern", + "survey_card_take_survey": "Teilnehmen", + "survey_dialog_title": "Umfrage öffnen", + "survey_dialog_content": "Wie möchtest du die Umfrage öffnen?", + "survey_dialog_not_interested": "Nicht teilnehmen", + "survey_dialog_open_in_app": "In App öffnen", + "survey_dialog_open_externally": "Im Browser öffnen", + "survey_dialog_already_completed": "Bereits abgeschlossen", + "survey_webview_title": "Nutzerumfrage", + "survey_error_opening_url": "Konnte die URL nicht öffnen", + "survey_completed_question": "Hast du die Umfrage abgeschlossen?", + "survey_completed_yes": "Ja, habe ich", + "survey_completed_no": "Nein, später", + "common_back": "Zurück", + "common_refresh": "Aktualisieren" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9c4ec29..d8d3b43 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -499,5 +499,25 @@ "type": "int" } } - } + }, + + "@_SURVEY": {}, + "survey_card_title": "User Survey", + "survey_card_subtitle": "MoveTopia Study Project", + "survey_card_description": "We would like to hear your opinion about MoveTopia. Your participation helps us improve the app.", + "survey_card_remind_later": "Remind me later", + "survey_card_take_survey": "Participate", + "survey_dialog_title": "Open Survey", + "survey_dialog_content": "How would you like to open the survey?", + "survey_dialog_not_interested": "Not interested", + "survey_dialog_open_in_app": "Open in app", + "survey_dialog_open_externally": "Open in browser", + "survey_dialog_already_completed": "Already completed", + "survey_webview_title": "User Survey", + "survey_error_opening_url": "Could not open the URL", + "survey_completed_question": "Did you complete the survey?", + "survey_completed_yes": "Yes, I did", + "survey_completed_no": "No, later", + "common_back": "Back", + "common_refresh": "Refresh" } \ No newline at end of file diff --git a/lib/presentation/profile/debug_settings/screen/debug_settings_screen.dart b/lib/presentation/profile/debug_settings/screen/debug_settings_screen.dart index 689a070..9b69487 100644 --- a/lib/presentation/profile/debug_settings/screen/debug_settings_screen.dart +++ b/lib/presentation/profile/debug_settings/screen/debug_settings_screen.dart @@ -10,6 +10,7 @@ import '../widgets/app_dates_section.dart'; import '../widgets/badge_debug_section.dart'; import '../widgets/cache_debug_section.dart'; import '../widgets/streak_debug_section.dart'; +import '../widgets/survey_debug_section.dart'; class DebugSettingsScreen extends HookConsumerWidget { const DebugSettingsScreen({super.key}); @@ -59,6 +60,9 @@ class DebugSettingsScreen extends HookConsumerWidget { // Badge Debugging Sektion BadgeDebugSection(isLoading: isLoading), + // Survey Debugging Sektion + SurveyDebugSection(isLoading: isLoading), + // Cache Debuggin Sektion CacheDebugSection(isLoading: isLoading) ], diff --git a/lib/presentation/profile/debug_settings/widgets/survey_debug_section.dart b/lib/presentation/profile/debug_settings/widgets/survey_debug_section.dart new file mode 100644 index 0000000..965f465 --- /dev/null +++ b/lib/presentation/profile/debug_settings/widgets/survey_debug_section.dart @@ -0,0 +1,224 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:movetopia/presentation/today/widgets/survey_card.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Debug Sektion for User Survey +class SurveyDebugSection extends ConsumerWidget { + final ValueNotifier isLoading; + + const SurveyDebugSection({super.key, required this.isLoading}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + + return Card( + margin: const EdgeInsets.symmetric(vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Survey Debug', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.primary, + ), + ), + const SizedBox(height: 16), + FutureBuilder>( + future: _getSurveyStatus(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } + + final status = + snapshot.data ?? {'dismissed': false, 'completed': false}; + final dismissed = status['dismissed'] ?? false; + final completed = status['completed'] ?? false; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Aktueller Status:', + style: theme.textTheme.titleMedium), + const SizedBox(height: 8), + _buildStatusChip(context, 'Dismissed', dismissed, + theme.colorScheme.error), + const SizedBox(height: 4), + _buildStatusChip(context, 'Completed', completed, + theme.colorScheme.primary), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.refresh), + label: const Text('Reset Status'), + onPressed: () => _resetSurveyStatus(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.check_circle), + label: const Text('Mark Completed'), + onPressed: () => _markSurveyCompleted(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.secondary, + foregroundColor: theme.colorScheme.onSecondary, + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.close), + label: const Text('Mark Dismissed'), + onPressed: () => _markSurveyDismissed(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.error, + foregroundColor: theme.colorScheme.onError, + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.date_range), + label: const Text('Set to Today'), + onPressed: () => _setStartDateToToday(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.tertiary, + foregroundColor: theme.colorScheme.onTertiary, + ), + ), + ], + ), + ], + ); + }, + ), + ], + ), + ), + ); + } + + /// Creates a status chip with a label and value + Widget _buildStatusChip( + BuildContext context, String label, bool value, Color color) { + return Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: value ? color : Colors.grey.shade300, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + label, + style: TextStyle( + color: value ? Colors.white : Colors.black54, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Text(value ? 'Ja' : 'Nein'), + ], + ); + } + + /// Reads the survey status from SharedPreferences + Future> _getSurveyStatus() async { + final prefs = await SharedPreferences.getInstance(); + final dismissed = + prefs.getBool(SurveyConstants.prefKeySurveyDismissed) ?? false; + final completed = + prefs.getBool(SurveyConstants.prefKeySurveyCompleted) ?? false; + + return { + 'dismissed': dismissed, + 'completed': completed, + }; + } + + /// Resets the survey status in SharedPreferences + Future _resetSurveyStatus(BuildContext context) async { + isLoading.value = true; + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(SurveyConstants.prefKeySurveyDismissed); + await prefs.remove(SurveyConstants.prefKeySurveyCompleted); + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Survey-Status zurückgesetzt')), + ); + } + } finally { + isLoading.value = false; + } + } + + /// Marks the survey as completed + Future _markSurveyCompleted(BuildContext context) async { + isLoading.value = true; + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(SurveyConstants.prefKeySurveyCompleted, true); + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Umfrage als abgeschlossen markiert')), + ); + } + } finally { + isLoading.value = false; + } + } + + /// Marks the survey as dismissed + Future _markSurveyDismissed(BuildContext context) async { + isLoading.value = true; + + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(SurveyConstants.prefKeySurveyDismissed, true); + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Umfrage als verworfen markiert')), + ); + } + } finally { + isLoading.value = false; + } + } + + /// Set the start date to today + Future _setStartDateToToday(BuildContext context) async { + isLoading.value = true; + + try { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'StartDate ist ein static final Feld und kann zur Laufzeit nicht geändert werden. Bitte ändere den Code direkt.')), + ); + } + } finally { + isLoading.value = false; + } + } +} diff --git a/lib/presentation/today/routes.dart b/lib/presentation/today/routes.dart index 4cd3754..2f937d0 100644 --- a/lib/presentation/today/routes.dart +++ b/lib/presentation/today/routes.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:movetopia/presentation/today/screen/survey_webview_screen.dart'; import 'package:movetopia/presentation/today/screen/today_screen.dart'; const todayPath = '/today'; +const String surveyWebViewPath = 'survey-webview'; +const String fullSurveyWebViewPath = '$todayPath/$surveyWebViewPath'; + class TodayRoutes { static final navigatorKey = GlobalKey(); @@ -11,6 +15,14 @@ class TodayRoutes { GoRoute( path: todayPath, builder: (context, state) => const TodayScreen(), - ) + routes: [ + GoRoute( + path: surveyWebViewPath, + builder: (context, state) => SurveyWebViewScreen( + surveyUrl: state.extra as String, + ), + ), + ], + ), ]; } diff --git a/lib/presentation/today/screen/survey_webview_screen.dart b/lib/presentation/today/screen/survey_webview_screen.dart new file mode 100644 index 0000000..43fbd79 --- /dev/null +++ b/lib/presentation/today/screen/survey_webview_screen.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import '../widgets/survey_card.dart'; + +/// A screen that displays a survey in a WebView +class SurveyWebViewScreen extends StatefulWidget { + /// The URL of the survey + final String surveyUrl; + + /// Creates a new [SurveyWebViewScreen] + const SurveyWebViewScreen({super.key, required this.surveyUrl}); + + @override + State createState() => _SurveyWebViewScreenState(); +} + +class _SurveyWebViewScreenState extends State { + late final WebViewController _controller; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onPageStarted: (String url) { + setState(() { + _isLoading = true; + }); + }, + onPageFinished: (String url) { + setState(() { + _isLoading = false; + }); + }, + ), + ) + ..loadRequest(Uri.parse(widget.surveyUrl)); + } + + /// Shows a dialog to confirm if the survey is completed + Future _showCompletionDialog(BuildContext context) async { + final l10n = AppLocalizations.of(context)!; + final completedSurvey = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.survey_completed_question), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.check_circle), + label: Text(l10n.survey_completed_yes), + onPressed: () => Navigator.of(context).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), + ), + const SizedBox(height: 8), + TextButton.icon( + icon: const Icon(Icons.close), + label: Text(l10n.survey_completed_no), + onPressed: () => Navigator.of(context).pop(false), + ), + ], + ), + ), + ); + + if (completedSurvey == true) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(SurveyConstants.prefKeySurveyCompleted, true); + } + } + + /// Opens the survey URL in the default browser + Future _openInBrowser() async { + final Uri url = Uri.parse(widget.surveyUrl); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + if (mounted) { + final l10n = AppLocalizations.of(context)!; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(l10n.survey_error_opening_url), + ), + ); + } + } + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + return Scaffold( + appBar: AppBar( + title: Text(l10n.survey_webview_title), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () async { + // Dialog anzeigen, bevor die Seite verlassen wird + await _showCompletionDialog(context); + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + actions: [ + IconButton( + icon: const Icon(Icons.open_in_browser), + onPressed: _openInBrowser, + tooltip: l10n.survey_dialog_open_externally, + ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () => _controller.reload(), + tooltip: l10n.common_refresh, + ), + ], + ), + body: Stack( + children: [ + WebViewWidget(controller: _controller), + if (_isLoading) + const Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/today/screen/today_screen.dart b/lib/presentation/today/screen/today_screen.dart index 59461fc..6a9369f 100644 --- a/lib/presentation/today/screen/today_screen.dart +++ b/lib/presentation/today/screen/today_screen.dart @@ -1,4 +1,5 @@ import 'dart:io' show Platform; + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -12,11 +13,13 @@ import 'package:movetopia/presentation/today/widgets/achievement_preview_card.da import 'package:movetopia/presentation/today/widgets/exercise_minutes_card.dart'; import 'package:movetopia/presentation/today/widgets/last_activity_card.dart'; import 'package:movetopia/presentation/today/widgets/steps_streak_card.dart'; +import 'package:movetopia/presentation/today/widgets/survey_card.dart'; import 'package:movetopia/presentation/today/widgets/tracking_active.dart'; import 'package:movetopia/presentation/today/widgets/weekly_steps_chart.dart'; import 'package:movetopia/presentation/tracking/routes.dart'; import 'package:movetopia/presentation/tracking/view_model/tracking_state.dart'; import 'package:movetopia/presentation/tracking/view_model/tracking_view_model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../../../core/health_authorized_view_model.dart'; import '../../challenges/routes.dart'; @@ -40,6 +43,8 @@ class TodayScreen extends HookConsumerWidget { final isLoading = useState(false); final refreshIndicatorKey = useRef(GlobalKey()); + final showSurvey = useState(true); + Future fetchHealthData() async { try { isLoading.value = true; @@ -111,6 +116,25 @@ class TodayScreen extends HookConsumerWidget { return null; }, const []); + useEffect(() { + SharedPreferences.getInstance().then((prefs) { + final isDismissed = + prefs.getBool(SurveyConstants.prefKeySurveyDismissed) ?? false; + final isCompleted = + prefs.getBool(SurveyConstants.prefKeySurveyCompleted) ?? false; + + final now = DateTime.now(); + final isSurveyStillActive = + now.isBefore(SurveyConstants.surveyEndDate) || + now.isAtSameMomentAs(SurveyConstants.surveyEndDate); + + if (isDismissed || isCompleted || !isSurveyStillActive) { + showSurvey.value = false; + } + }); + return null; + }, const []); + return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, appBar: AppBar( @@ -142,7 +166,8 @@ class TodayScreen extends HookConsumerWidget { stopTracking, pauseTracking, navigateToBadges, - refreshIndicatorKey.value), + refreshIndicatorKey.value, + showSurvey), floatingActionButton: trackingState?.isRecording == false && Platform.isAndroid ? FloatingActionButton( @@ -165,6 +190,7 @@ Widget _buildBody( Future Function() pauseTracking, void Function() navigateToBadges, GlobalKey refreshIndicatorKey, + ValueNotifier showSurvey, ) { final theme = Theme.of(context); final l10n = AppLocalizations.of(context)!; @@ -180,6 +206,16 @@ Widget _buildBody( child: ListView( padding: const EdgeInsets.symmetric(vertical: 16.0), children: [ + if (showSurvey.value) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: SurveyCard( + onDismiss: () { + showSurvey.value = false; + }, + ), + ), + // Today's Section _buildSectionHeader(context, l10n.navigation_today, theme), const SizedBox(height: 12), diff --git a/lib/presentation/today/widgets/survey_card.dart b/lib/presentation/today/widgets/survey_card.dart new file mode 100644 index 0000000..c8d31ce --- /dev/null +++ b/lib/presentation/today/widgets/survey_card.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:movetopia/presentation/common/widgets/generic_card.dart'; +import 'package:movetopia/presentation/today/routes.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Constants for the survey feature +class SurveyConstants { + /// The end date for the survey + static final DateTime surveyEndDate = DateTime(2025, 5, 19); + + /// The survey URL + static const String surveyUrl = 'https://forms.gle/QY8HnRF2LFnQqRJ18'; + + /// Keys for shared preferences + static const String prefKeySurveyDismissed = 'survey_dismissed'; + static const String prefKeySurveyCompleted = 'survey_completed'; +} + +/// A card that promotes the user survey +class SurveyCard extends StatelessWidget { + /// Callback, if the card is dismissed + final VoidCallback onDismiss; + + const SurveyCard({ + super.key, + required this.onDismiss, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + + return GenericCard( + title: l10n.survey_card_title, + subtitles: [l10n.survey_card_subtitle], + iconData: Icons.assignment, + color: theme.colorScheme.tertiary, + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.survey_card_description, + style: theme.textTheme.bodyMedium, + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + _dismissSurvey(context, temporarily: true); + onDismiss(); + }, + child: Text(l10n.survey_card_remind_later), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () => _showSurveyOptions(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.tertiary, + foregroundColor: theme.colorScheme.onTertiary, + ), + child: Text(l10n.survey_card_take_survey), + ), + ], + ), + ], + ), + ); + } + + /// Shows a dialog with options to open the survey + Future _showSurveyOptions(BuildContext context) async { + final l10n = AppLocalizations.of(context)!; + final theme = Theme.of(context); + + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.survey_dialog_title), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.survey_dialog_content), + const SizedBox(height: 16), + _dialogOptionButton( + context, + icon: Icons.web, + label: l10n.survey_dialog_open_in_app, + onTap: () { + Navigator.of(context).pop(); + _openSurveyInWebView(context); + }, + color: theme.colorScheme.primary, + ), + const SizedBox(height: 8), + _dialogOptionButton( + context, + icon: Icons.open_in_browser, + label: l10n.survey_dialog_open_externally, + onTap: () { + Navigator.of(context).pop(); + _openSurveyExternally(context); + }, + color: theme.colorScheme.tertiary, + ), + const SizedBox(height: 8), + _dialogOptionButton( + context, + icon: Icons.check_circle_outline, + label: l10n.survey_dialog_already_completed, + onTap: () { + Navigator.of(context).pop(); + _markAsCompleted(context); + }, + color: theme.colorScheme.secondary, + ), + const SizedBox(height: 8), + _dialogOptionButton( + context, + icon: Icons.close, + label: l10n.survey_dialog_not_interested, + onTap: () { + Navigator.of(context).pop(); + _dismissSurvey(context, temporarily: false); + onDismiss(); + }, + color: theme.colorScheme.error, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n.common_close), + ), + ], + ), + ); + } + + /// Helper to create a dialog option button + Widget _dialogOptionButton( + BuildContext context, { + required IconData icon, + required String label, + required VoidCallback onTap, + required Color color, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + border: Border.all(color: color), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(icon, color: color, size: 20), + const SizedBox(width: 12), + Expanded( + child: Text( + label, + style: TextStyle(color: color), + ), + ), + ], + ), + ), + ); + } + + /// Opens the survey in an in-app WebView + Future _openSurveyInWebView(BuildContext context) async { + if (context.mounted) { + context.push(fullSurveyWebViewPath, extra: SurveyConstants.surveyUrl); + + Future.delayed(const Duration(milliseconds: 300), () { + if (context.mounted) { + onDismiss(); + } + }); + } + } + + /// Opens the survey in an external browser + Future _openSurveyExternally(BuildContext context) async { + final Uri url = Uri.parse(SurveyConstants.surveyUrl); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + + if (context.mounted) { + await Future.delayed(const Duration(seconds: 1)); + await _showCompletionDialog(context); + } + onDismiss(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text(AppLocalizations.of(context)!.survey_error_opening_url), + ), + ); + } + } + } + + /// Shows a dialog to confirm if the survey is completed + Future _showCompletionDialog(BuildContext context) async { + final l10n = AppLocalizations.of(context)!; + final completedSurvey = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.survey_completed_question), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.check_circle), + label: Text(l10n.survey_completed_yes), + onPressed: () => Navigator.of(context).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + ), + ), + const SizedBox(height: 8), + TextButton.icon( + icon: const Icon(Icons.close), + label: Text(l10n.survey_completed_no), + onPressed: () => Navigator.of(context).pop(false), + ), + ], + ), + ), + ); + + // Only mark as completed if the user confirms + if (completedSurvey == true) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(SurveyConstants.prefKeySurveyCompleted, true); + } + } + + /// Marks the survey as completed + Future _markAsCompleted(BuildContext context) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(SurveyConstants.prefKeySurveyCompleted, true); + onDismiss(); + } + + /// Dismisses the survey card + Future _dismissSurvey(BuildContext context, + {required bool temporarily}) async { + final prefs = await SharedPreferences.getInstance(); + + if (temporarily) { + } else { + await prefs.setBool(SurveyConstants.prefKeySurveyDismissed, true); + if (context.mounted && Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + } + } +} + +/// Helper functions to check if the survey should be shown +class SurveyHelper { + /// Checks if the current date is before or at the survey end date + static bool isSurveyAvailable() { + final now = DateTime.now(); + // Prüfe nur, ob das aktuelle Datum vor dem Enddatum liegt + return now.isBefore(SurveyConstants.surveyEndDate) || + now.isAtSameMomentAs(SurveyConstants.surveyEndDate); + } + + /// Checks if the survey should be shown based on preferences and date + static Future shouldShowSurvey() async { + // Check date first + if (!isSurveyAvailable()) { + return false; + } + + // Check preferences + final prefs = await SharedPreferences.getInstance(); + final isDismissed = + prefs.getBool(SurveyConstants.prefKeySurveyDismissed) ?? false; + final isCompleted = + prefs.getBool(SurveyConstants.prefKeySurveyCompleted) ?? false; + + // Show if not dismissed and not completed + return !isDismissed && !isCompleted; + } +} diff --git a/pubspec.lock b/pubspec.lock index 26aacab..0395e64 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1219,6 +1219,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: caf0f5a1012aa3c2d33c4215adc72dc1194bb59a2d3ed901f457965626805e66 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "5c3b6f992d123084903ec091b84f021c413a92a9af49038e4564a1b26c8452cf" + url: "https://pub.dev" + source: hosted + version: "4.4.1" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "18b1640839cf6546784a524c72aded5b6e86b23e7167dc2311cc96f7658b64bd" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: c9f9be526fa0d3347374ceaa05c4b3acb85f4f112abd62f7d74b7d301fa515ff + url: "https://pub.dev" + source: hosted + version: "3.20.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2f18e4a..d633697 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: app_settings: ^6.1.1 url_launcher: ^6.2.5 geolocator: ^13.0.4 + webview_flutter: ^4.8.0 dev_dependencies: flutter_test: