Skip to content

Commit 1e9fec7

Browse files
committed
Add user prompt to optionally skip tutorial on first launch
1 parent d802b00 commit 1e9fec7

File tree

3 files changed

+169
-1
lines changed

3 files changed

+169
-1
lines changed

lib/app/modules/home/views/home_page_body.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:get/get.dart';
44
import 'package:taskwarrior/app/modules/home/views/show_tasks.dart';
55
import 'package:taskwarrior/app/modules/home/views/show_tasks_replica.dart';
66
import 'package:taskwarrior/app/modules/home/views/tasks_builder.dart';
7+
import 'package:taskwarrior/app/modules/home/views/tutorial_modal.dart';
8+
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
79
import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart';
810
import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
911
import 'package:taskwarrior/app/utils/language/sentence_manager.dart';
@@ -13,10 +15,44 @@ class HomePageBody extends StatelessWidget {
1315
final HomeController controller;
1416
const HomePageBody({required this.controller, super.key});
1517

18+
void _showTutorialModal(BuildContext context) {
19+
Future.delayed(
20+
const Duration(milliseconds: 500),
21+
() async {
22+
bool promptShown = await SaveTourStatus.getTutorialPromptShown();
23+
if (!promptShown && context.mounted) {
24+
showDialog(
25+
context: context,
26+
barrierDismissible: false,
27+
builder: (BuildContext dialogContext) {
28+
return TutorialModal(
29+
onYes: () async {
30+
await SaveTourStatus.saveTutorialPromptShown(true);
31+
if (dialogContext.mounted) {
32+
Navigator.of(dialogContext).pop();
33+
controller.initInAppTour();
34+
controller.tutorialCoachMark.show(context: context);
35+
}
36+
},
37+
onNo: () async {
38+
await SaveTourStatus.saveTutorialPromptShown(true);
39+
await SaveTourStatus.saveInAppTourStatus(true);
40+
if (dialogContext.mounted) {
41+
Navigator.of(dialogContext).pop();
42+
}
43+
},
44+
);
45+
},
46+
);
47+
}
48+
},
49+
);
50+
}
51+
1652
@override
1753
Widget build(BuildContext context) {
1854
controller.initInAppTour();
19-
controller.showInAppTour(context);
55+
_showTutorialModal(context);
2056
TaskwarriorColorTheme tColors =
2157
Theme.of(context).extension<TaskwarriorColorTheme>()!;
2258
return DoubleBackToCloseApp(
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:google_fonts/google_fonts.dart';
3+
import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
4+
5+
class TutorialModal extends StatelessWidget {
6+
final VoidCallback onYes;
7+
final VoidCallback onNo;
8+
9+
const TutorialModal({
10+
super.key,
11+
required this.onYes,
12+
required this.onNo,
13+
});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
TaskwarriorColorTheme tColors =
18+
Theme.of(context).extension<TaskwarriorColorTheme>()!;
19+
20+
return Dialog(
21+
shape: RoundedRectangleBorder(
22+
borderRadius: BorderRadius.circular(20),
23+
),
24+
elevation: 10,
25+
backgroundColor: tColors.dialogBackgroundColor,
26+
child: Padding(
27+
padding: const EdgeInsets.all(24.0),
28+
child: Column(
29+
mainAxisSize: MainAxisSize.min,
30+
children: [
31+
// Icon
32+
Container(
33+
padding: const EdgeInsets.all(16),
34+
decoration: BoxDecoration(
35+
color: tColors.primaryBackgroundColor?.withValues(alpha: 0.3),
36+
shape: BoxShape.circle,
37+
),
38+
child: Icon(
39+
Icons.school_outlined,
40+
size: 48,
41+
color: tColors.primaryTextColor,
42+
),
43+
),
44+
const SizedBox(height: 20),
45+
46+
// Title
47+
Text(
48+
'Welcome!',
49+
style: GoogleFonts.poppins(
50+
fontSize: 24,
51+
fontWeight: FontWeight.bold,
52+
color: tColors.primaryTextColor,
53+
),
54+
),
55+
const SizedBox(height: 12),
56+
57+
// Message
58+
Text(
59+
'Would you like to see a quick tutorial to learn how to use this app?',
60+
textAlign: TextAlign.center,
61+
style: GoogleFonts.poppins(
62+
fontSize: 16,
63+
color: tColors.primaryTextColor?.withValues(alpha: 0.8),
64+
height: 1.5,
65+
),
66+
),
67+
const SizedBox(height: 28),
68+
69+
// Buttons
70+
Row(
71+
children: [
72+
Expanded(
73+
child: OutlinedButton(
74+
onPressed: onNo,
75+
style: OutlinedButton.styleFrom(
76+
padding: const EdgeInsets.symmetric(vertical: 14),
77+
side: BorderSide(
78+
color: tColors.primaryTextColor!.withValues(alpha: 0.3),
79+
width: 1.5,
80+
),
81+
shape: RoundedRectangleBorder(
82+
borderRadius: BorderRadius.circular(12),
83+
),
84+
),
85+
child: Text(
86+
'No, thanks',
87+
style: GoogleFonts.poppins(
88+
fontSize: 15,
89+
fontWeight: FontWeight.w500,
90+
color: tColors.primaryTextColor,
91+
),
92+
),
93+
),
94+
),
95+
const SizedBox(width: 12),
96+
Expanded(
97+
child: ElevatedButton(
98+
onPressed: onYes,
99+
style: ElevatedButton.styleFrom(
100+
padding: const EdgeInsets.symmetric(vertical: 14),
101+
backgroundColor: tColors.primaryBackgroundColor,
102+
shape: RoundedRectangleBorder(
103+
borderRadius: BorderRadius.circular(12),
104+
),
105+
elevation: 2,
106+
),
107+
child: Text(
108+
'Show Tutorial',
109+
style: GoogleFonts.poppins(
110+
fontSize: 15,
111+
fontWeight: FontWeight.w600,
112+
color: tColors.primaryTextColor,
113+
),
114+
),
115+
),
116+
),
117+
],
118+
),
119+
],
120+
),
121+
),
122+
);
123+
}
124+
}

lib/app/utils/app_settings/save_tour_status.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,12 @@ class SaveTourStatus {
6262
static Future<bool> getTaskSwipeTutorialStatus() async {
6363
return _preferences?.getBool('task_swipe_tutorial_completed') ?? false;
6464
}
65+
66+
static Future saveTutorialPromptShown(bool status) async {
67+
await _preferences?.setBool('tutorial_prompt_shown', status);
68+
}
69+
70+
static Future<bool> getTutorialPromptShown() async {
71+
return _preferences?.getBool('tutorial_prompt_shown') ?? false;
72+
}
6573
}

0 commit comments

Comments
 (0)