Skip to content

Commit d06436e

Browse files
Mailos07andrewtavisangrezichatterbox
authored
feat: add interactive tutorial onboarding flow (#605)
* initial commit * fix: rename lambda params to present tense and add modifier params * fix: add focused input field to WrongKeyboardScreen so keyboard appears * fix: navigation to tutorial page done through quick tutorial button * refactor: replace custom color constants with MaterialTheme colors in TutorialHomeScreen * refactor: enhance TutorialStepScreen with lifecycle-aware keyboard state management and update color scheme to use MaterialTheme * refactor: update WrongKeyboardScreen to use MaterialTheme colors for improved consistency and readability * refactor: extract TutorialChapter data class into its own file for better organization and maintainability --------- Co-authored-by: Andrew Tavis McAllister <andrew.t.mcallister@gmail.com> Co-authored-by: angrezichatterbox <gouthammohanraj@gmail.com>
1 parent 7ec4de8 commit d06436e

7 files changed

Lines changed: 995 additions & 0 deletions

File tree

app/src/main/java/be/scri/ui/screens/InstallationScreen.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import androidx.compose.material3.MaterialTheme
2929
import androidx.compose.material3.OutlinedButton
3030
import androidx.compose.material3.Text
3131
import androidx.compose.runtime.Composable
32+
import androidx.compose.runtime.getValue
33+
import androidx.compose.runtime.mutableStateOf
34+
import androidx.compose.runtime.remember
35+
import androidx.compose.runtime.setValue
3236
import androidx.compose.ui.Alignment
3337
import androidx.compose.ui.Modifier
3438
import androidx.compose.ui.draw.alpha
@@ -49,6 +53,7 @@ import androidx.compose.ui.unit.dp
4953
import androidx.compose.ui.unit.sp
5054
import be.scri.R
5155
import be.scri.ui.common.ScribeBaseScreen
56+
import be.scri.ui.screens.tutorial.TutorialNavigator
5257

5358
/**
5459
* The installation page of the application with details for installing Scribe keyboards and downloading data.
@@ -61,6 +66,15 @@ fun InstallationScreen(
6166
onNavigateToDownloadData: () -> Unit,
6267
modifier: Modifier = Modifier,
6368
) {
69+
var showTutorial by remember { mutableStateOf(false) }
70+
71+
if (showTutorial) {
72+
TutorialNavigator(
73+
onTutorialExit = { showTutorial = false },
74+
)
75+
return
76+
}
77+
6478
val layoutDirection = LocalLayoutDirection.current
6579
val localConfiguration = LocalConfiguration.current
6680
val resource: Int =
@@ -291,6 +305,7 @@ fun InstallationScreen(
291305

292306
OutlinedButton(
293307
onClick = {
308+
showTutorial = true
294309
},
295310
modifier =
296311
Modifier
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package be.scri.ui.screens.tutorial
4+
5+
/**
6+
* Represents a single tutorial chapter in the home screen.
7+
*
8+
* @property title The display name of the chapter.
9+
* @property chapterIndex The index used to navigate to this chapter.
10+
*/
11+
data class TutorialChapter(
12+
val title: String,
13+
val chapterIndex: Int,
14+
)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package be.scri.ui.screens.tutorial
4+
5+
/**
6+
* Defines all tutorial chapters and their steps.
7+
* Each chapter contains one or more interactive steps that guide the user
8+
* through a specific Scribe feature.
9+
*/
10+
object TutorialContent {
11+
/**
12+
* Chapter 1: Noun Annotation.
13+
* Teaches users about gender tags that appear when typing nouns.
14+
*/
15+
val nounAnnotationSteps =
16+
listOf(
17+
TutorialStep(
18+
instruction =
19+
"Write the word \"Vater\". Notice the word suggestions " +
20+
"that appear on the keyboard's top bar.\n\n" +
21+
"Then, press space. You will see the word's gender " +
22+
"tag on the keyboard's top bar \u2013 in this case, \"M\" for Maskulin.",
23+
expectedWord = "Vater",
24+
),
25+
TutorialStep(
26+
instruction =
27+
"Now write the word \"Mutter\" and then press space. " +
28+
"The gender tag will be \"F\", for Feminin.",
29+
expectedWord = "Mutter",
30+
),
31+
)
32+
33+
/**
34+
* Chapter 2: Word Translation.
35+
* Teaches users how to use the Translate command via the Scribe key.
36+
*/
37+
val wordTranslationSteps =
38+
listOf(
39+
TutorialStep(
40+
instruction =
41+
"Let's translate! Tap the \u27A1 Scribe key on the top-left " +
42+
"corner of your keyboard, and select \u00DCbersetzen.\n\n" +
43+
"Then write the word you want to translate, press \u25B6, " +
44+
"and the translation will be returned to you.",
45+
hint = "If your second language is not German, change the language in your keyboard.",
46+
requiresValidation = false,
47+
),
48+
)
49+
50+
val verbConjugationSteps =
51+
listOf(
52+
TutorialStep(
53+
instruction =
54+
"On to the verbs. Tap the \u27A1 Scribe key on the top-left " +
55+
"corner of your keyboard, and select Konjugieren.\n\n" +
56+
"Write the verb you want to conjugate, press \u25B6, and " +
57+
"you will see a table with all the verb tenses. Select " +
58+
"the one you need and it will be inserted!",
59+
hint = "If your second language is not German, change the language in your keyboard.",
60+
requiresValidation = false,
61+
),
62+
)
63+
64+
val nounPluralsSteps =
65+
listOf(
66+
TutorialStep(
67+
instruction =
68+
"Finding the plural of a noun with Scribe is easy. Tap " +
69+
"the \u27A1 Scribe key on the top-left corner of your " +
70+
"keyboard, and select Plural.\n\n" +
71+
"Then write the noun you want the plural for, press " +
72+
"\u25B6, and the plural will be returned to you.",
73+
hint = "If your second language is not German, change the language in your keyboard.",
74+
requiresValidation = false,
75+
),
76+
)
77+
78+
/** Returns all chapters as a list of pairs (title, steps). */
79+
fun getAllChapters(): List<Pair<String, List<TutorialStep>>> =
80+
listOf(
81+
"Noun annotation" to nounAnnotationSteps,
82+
"Word translation" to wordTranslationSteps,
83+
"Verb conjugation" to verbConjugationSteps,
84+
"Noun plurals" to nounPluralsSteps,
85+
)
86+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package be.scri.ui.screens.tutorial
4+
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.isSystemInDarkTheme
8+
import androidx.compose.foundation.layout.Arrangement
9+
import androidx.compose.foundation.layout.Column
10+
import androidx.compose.foundation.layout.Row
11+
import androidx.compose.foundation.layout.Spacer
12+
import androidx.compose.foundation.layout.fillMaxSize
13+
import androidx.compose.foundation.layout.fillMaxWidth
14+
import androidx.compose.foundation.layout.height
15+
import androidx.compose.foundation.layout.padding
16+
import androidx.compose.foundation.layout.size
17+
import androidx.compose.foundation.shape.RoundedCornerShape
18+
import androidx.compose.material.icons.Icons
19+
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
20+
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
21+
import androidx.compose.material3.Button
22+
import androidx.compose.material3.ButtonDefaults
23+
import androidx.compose.material3.Card
24+
import androidx.compose.material3.CardDefaults
25+
import androidx.compose.material3.HorizontalDivider
26+
import androidx.compose.material3.Icon
27+
import androidx.compose.material3.MaterialTheme
28+
import androidx.compose.material3.Text
29+
import androidx.compose.runtime.Composable
30+
import androidx.compose.ui.Alignment
31+
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.graphics.Color
33+
import androidx.compose.ui.text.font.FontWeight
34+
import androidx.compose.ui.unit.dp
35+
import androidx.compose.ui.unit.sp
36+
37+
/**
38+
* The tutorial home screen (Screen 0.0 from Figma).
39+
* Displays a list of tutorial chapters and a button to start the full tutorial.
40+
* This screen is accessible from the About tab.
41+
*
42+
* @param onBackPress Callback when the back button is pressed.
43+
* @param onChapterSelect Callback when a specific chapter is tapped.
44+
* @param onStartFullTutorial Callback when the "Start full tutorial" button is pressed.
45+
* @param modifier Modifier for this composable.
46+
*/
47+
@Composable
48+
fun TutorialHomeScreen(
49+
onBackPress: () -> Unit,
50+
onChapterSelect: (Int) -> Unit,
51+
onStartFullTutorial: () -> Unit,
52+
modifier: Modifier = Modifier,
53+
) {
54+
val isDarkTheme = isSystemInDarkTheme()
55+
val backgroundColor = MaterialTheme.colorScheme.background
56+
val cardBackground = MaterialTheme.colorScheme.surface
57+
val textColor = MaterialTheme.colorScheme.onSurface
58+
val secondaryTextColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
59+
val dividerColor = MaterialTheme.colorScheme.outlineVariant
60+
61+
val chapters =
62+
listOf(
63+
TutorialChapter("Noun annotation", 0),
64+
TutorialChapter("Word translation", 1),
65+
TutorialChapter("Verb conjugation", 2),
66+
TutorialChapter("Noun plurals", 3),
67+
)
68+
69+
Column(
70+
modifier =
71+
modifier
72+
.fillMaxSize()
73+
.background(backgroundColor)
74+
.padding(16.dp),
75+
) {
76+
// Back button
77+
Row(
78+
verticalAlignment = Alignment.CenterVertically,
79+
modifier = Modifier.clickable { onBackPress() },
80+
) {
81+
Icon(
82+
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
83+
contentDescription = "Back",
84+
tint = MaterialTheme.colorScheme.primary,
85+
modifier = Modifier.size(24.dp),
86+
)
87+
Text(
88+
text = "Home",
89+
color = MaterialTheme.colorScheme.primary,
90+
fontSize = 16.sp,
91+
)
92+
}
93+
94+
Spacer(modifier = Modifier.height(24.dp))
95+
96+
// Info banner
97+
Card(
98+
shape = RoundedCornerShape(12.dp),
99+
colors = CardDefaults.cardColors(containerColor = cardBackground),
100+
modifier = Modifier.fillMaxWidth(),
101+
) {
102+
Row(
103+
modifier = Modifier.padding(16.dp),
104+
verticalAlignment = Alignment.CenterVertically,
105+
) {
106+
Text(
107+
text = "\uD83D\uDCA1",
108+
fontSize = 20.sp,
109+
modifier = Modifier.padding(end = 12.dp),
110+
)
111+
Text(
112+
text = "Make sure you select the desired Scribe keyboard by pressing \uD83C\uDF10 when typing.",
113+
color = textColor,
114+
fontSize = 14.sp,
115+
modifier = Modifier.weight(1f),
116+
)
117+
}
118+
}
119+
120+
Spacer(modifier = Modifier.height(16.dp))
121+
122+
// Intro text
123+
Card(
124+
shape = RoundedCornerShape(12.dp),
125+
colors = CardDefaults.cardColors(containerColor = cardBackground),
126+
modifier = Modifier.fillMaxWidth(),
127+
) {
128+
Text(
129+
text = "This quick tutorial will show you how to use Scribe to support writing in your second language.",
130+
color = textColor,
131+
fontSize = 14.sp,
132+
modifier = Modifier.padding(16.dp),
133+
)
134+
}
135+
136+
Spacer(modifier = Modifier.height(24.dp))
137+
138+
// Tutorial chapters header
139+
Text(
140+
text = "Tutorial chapters",
141+
color = textColor,
142+
fontSize = 18.sp,
143+
fontWeight = FontWeight.Bold,
144+
)
145+
146+
Spacer(modifier = Modifier.height(12.dp))
147+
148+
// Chapter list
149+
Card(
150+
shape = RoundedCornerShape(12.dp),
151+
colors = CardDefaults.cardColors(containerColor = cardBackground),
152+
modifier = Modifier.fillMaxWidth(),
153+
) {
154+
Column {
155+
chapters.forEachIndexed { index, chapter ->
156+
Row(
157+
modifier =
158+
Modifier
159+
.fillMaxWidth()
160+
.clickable { onChapterSelect(chapter.chapterIndex) }
161+
.padding(horizontal = 16.dp, vertical = 14.dp),
162+
horizontalArrangement = Arrangement.SpaceBetween,
163+
verticalAlignment = Alignment.CenterVertically,
164+
) {
165+
Text(
166+
text = chapter.title,
167+
color = textColor,
168+
fontSize = 16.sp,
169+
)
170+
Icon(
171+
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
172+
contentDescription = "Go to ${chapter.title}",
173+
tint = secondaryTextColor,
174+
)
175+
}
176+
if (index < chapters.size - 1) {
177+
HorizontalDivider(
178+
color = dividerColor,
179+
modifier = Modifier.padding(horizontal = 16.dp),
180+
)
181+
}
182+
}
183+
}
184+
}
185+
186+
Spacer(modifier = Modifier.weight(1f))
187+
188+
// Start full tutorial button
189+
Button(
190+
onClick = onStartFullTutorial,
191+
colors =
192+
ButtonDefaults.buttonColors(
193+
containerColor = MaterialTheme.colorScheme.primary,
194+
contentColor = if (isDarkTheme) Color.White else Color.Black,
195+
),
196+
shape = RoundedCornerShape(12.dp),
197+
modifier =
198+
Modifier
199+
.fillMaxWidth()
200+
.height(52.dp),
201+
) {
202+
Text(
203+
text = "Start full tutorial",
204+
fontSize = 18.sp,
205+
fontWeight = FontWeight.SemiBold,
206+
)
207+
}
208+
209+
Spacer(modifier = Modifier.height(16.dp))
210+
}
211+
}

0 commit comments

Comments
 (0)