From 1e9fec7d3e5950e13fe554da47fa46bba3331c0f Mon Sep 17 00:00:00 2001 From: Mohit Maulekhi Date: Sun, 21 Dec 2025 16:02:47 +0530 Subject: [PATCH 1/2] Add user prompt to optionally skip tutorial on first launch --- .../modules/home/views/home_page_body.dart | 38 +++++- .../modules/home/views/tutorial_modal.dart | 124 ++++++++++++++++++ .../utils/app_settings/save_tour_status.dart | 8 ++ 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 lib/app/modules/home/views/tutorial_modal.dart diff --git a/lib/app/modules/home/views/home_page_body.dart b/lib/app/modules/home/views/home_page_body.dart index a409e8ee..9314333e 100644 --- a/lib/app/modules/home/views/home_page_body.dart +++ b/lib/app/modules/home/views/home_page_body.dart @@ -4,6 +4,8 @@ import 'package:get/get.dart'; import 'package:taskwarrior/app/modules/home/views/show_tasks.dart'; import 'package:taskwarrior/app/modules/home/views/show_tasks_replica.dart'; import 'package:taskwarrior/app/modules/home/views/tasks_builder.dart'; +import 'package:taskwarrior/app/modules/home/views/tutorial_modal.dart'; +import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; @@ -13,10 +15,44 @@ class HomePageBody extends StatelessWidget { final HomeController controller; const HomePageBody({required this.controller, super.key}); + void _showTutorialModal(BuildContext context) { + Future.delayed( + const Duration(milliseconds: 500), + () async { + bool promptShown = await SaveTourStatus.getTutorialPromptShown(); + if (!promptShown && context.mounted) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return TutorialModal( + onYes: () async { + await SaveTourStatus.saveTutorialPromptShown(true); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + controller.initInAppTour(); + controller.tutorialCoachMark.show(context: context); + } + }, + onNo: () async { + await SaveTourStatus.saveTutorialPromptShown(true); + await SaveTourStatus.saveInAppTourStatus(true); + if (dialogContext.mounted) { + Navigator.of(dialogContext).pop(); + } + }, + ); + }, + ); + } + }, + ); + } + @override Widget build(BuildContext context) { controller.initInAppTour(); - controller.showInAppTour(context); + _showTutorialModal(context); TaskwarriorColorTheme tColors = Theme.of(context).extension()!; return DoubleBackToCloseApp( diff --git a/lib/app/modules/home/views/tutorial_modal.dart b/lib/app/modules/home/views/tutorial_modal.dart new file mode 100644 index 00000000..dc0e7d02 --- /dev/null +++ b/lib/app/modules/home/views/tutorial_modal.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; + +class TutorialModal extends StatelessWidget { + final VoidCallback onYes; + final VoidCallback onNo; + + const TutorialModal({ + super.key, + required this.onYes, + required this.onNo, + }); + + @override + Widget build(BuildContext context) { + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + elevation: 10, + backgroundColor: tColors.dialogBackgroundColor, + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Icon + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: tColors.primaryBackgroundColor?.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + child: Icon( + Icons.school_outlined, + size: 48, + color: tColors.primaryTextColor, + ), + ), + const SizedBox(height: 20), + + // Title + Text( + 'Welcome!', + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.bold, + color: tColors.primaryTextColor, + ), + ), + const SizedBox(height: 12), + + // Message + Text( + 'Would you like to see a quick tutorial to learn how to use this app?', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 16, + color: tColors.primaryTextColor?.withValues(alpha: 0.8), + height: 1.5, + ), + ), + const SizedBox(height: 28), + + // Buttons + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: onNo, + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + side: BorderSide( + color: tColors.primaryTextColor!.withValues(alpha: 0.3), + width: 1.5, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'No, thanks', + style: GoogleFonts.poppins( + fontSize: 15, + fontWeight: FontWeight.w500, + color: tColors.primaryTextColor, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: onYes, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + backgroundColor: tColors.primaryBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, + ), + child: Text( + 'Show Tutorial', + style: GoogleFonts.poppins( + fontSize: 15, + fontWeight: FontWeight.w600, + color: tColors.primaryTextColor, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/utils/app_settings/save_tour_status.dart b/lib/app/utils/app_settings/save_tour_status.dart index 11b23689..adc45daf 100644 --- a/lib/app/utils/app_settings/save_tour_status.dart +++ b/lib/app/utils/app_settings/save_tour_status.dart @@ -62,4 +62,12 @@ class SaveTourStatus { static Future getTaskSwipeTutorialStatus() async { return _preferences?.getBool('task_swipe_tutorial_completed') ?? false; } + + static Future saveTutorialPromptShown(bool status) async { + await _preferences?.setBool('tutorial_prompt_shown', status); + } + + static Future getTutorialPromptShown() async { + return _preferences?.getBool('tutorial_prompt_shown') ?? false; + } } From b7e26a7866d564d041cc177d7a27348418a9d4c2 Mon Sep 17 00:00:00 2001 From: Mohit Maulekhi Date: Sun, 21 Dec 2025 16:12:02 +0530 Subject: [PATCH 2/2] all tutorial will be turned off if user selects no --- lib/app/modules/home/views/home_page_body.dart | 2 +- lib/app/utils/app_settings/save_tour_status.dart | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/app/modules/home/views/home_page_body.dart b/lib/app/modules/home/views/home_page_body.dart index 9314333e..508c831d 100644 --- a/lib/app/modules/home/views/home_page_body.dart +++ b/lib/app/modules/home/views/home_page_body.dart @@ -36,7 +36,7 @@ class HomePageBody extends StatelessWidget { }, onNo: () async { await SaveTourStatus.saveTutorialPromptShown(true); - await SaveTourStatus.saveInAppTourStatus(true); + await SaveTourStatus.disableAllTutorials(); if (dialogContext.mounted) { Navigator.of(dialogContext).pop(); } diff --git a/lib/app/utils/app_settings/save_tour_status.dart b/lib/app/utils/app_settings/save_tour_status.dart index adc45daf..1d669f34 100644 --- a/lib/app/utils/app_settings/save_tour_status.dart +++ b/lib/app/utils/app_settings/save_tour_status.dart @@ -70,4 +70,14 @@ class SaveTourStatus { static Future getTutorialPromptShown() async { return _preferences?.getBool('tutorial_prompt_shown') ?? false; } + + static Future disableAllTutorials() async { + await saveReportsTourStatus(true); + await saveInAppTourStatus(true); + await saveFilterTourStatus(true); + await saveProfileTourStatus(true); + await saveDetailsTourStatus(true); + await saveManageTaskServerTourStatus(true); + await saveTaskSwipeTutorialStatus(true); + } }