Skip to content

Commit 0228abb

Browse files
authored
[MBL-19455][Student] Implement Welcome Widget with time-based greetings (#3404)
## Summary Implements the Welcome Widget for the Student app dashboard with time-based personalized greetings and motivational messages. **Key Features:** - Time-based greetings (Morning 4am-12pm, Afternoon 12pm-5pm, Evening 5pm-9pm, Night 9pm-4am) - Personalized with user's first name - 88 motivational messages (52 generic + 36 time-specific) - Pull-to-refresh updates greeting and message - Full TalkBack accessibility support - Comprehensive unit test coverage (37 tests) **Architecture:** - Implemented in `pandautils` for cross-app reusability - MVVM with Use Cases pattern - Hilt dependency injection - Testable design with TimeProvider abstraction ## Test Plan 1. Log in to the Student app 2. Navigate to Dashboard 3. Verify Welcome Widget displays with time-appropriate greeting and user's first name 4. Pull down to refresh the dashboard 5. Verify the motivational message changes 6. Test at different times of day to verify greeting changes: - 4am-12pm: "Good morning" - 12pm-5pm: "Good afternoon" - 5pm-9pm: "Good evening" - 9pm-4am: "Good night" 7. Enable TalkBack and verify widget is accessible 8. Run unit tests: `./gradle/gradlew -p apps :pandautils:testDebugUnitTest --tests "com.instructure.pandautils.features.dashboard.widget.welcome.*"` refs: MBL-19455 affects: Student release note: Added personalized welcome widget with time-based greetings and motivational messages ## Checklist - [x] Unit tests added and passing (37 tests, 4 test suites) - [x] Dark/light mode compatible (uses theme colors) - [x] Accessibility support (TalkBack content descriptions) - [x] Tablet/landscape layout responsive - [x] Deployed and manually tested on device
1 parent c20cee8 commit 0228abb

File tree

16 files changed

+995
-16
lines changed

16 files changed

+995
-16
lines changed

apps/student/src/main/java/com/instructure/student/features/dashboard/compose/DashboardScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ import com.instructure.pandautils.compose.composables.Loading
5959
import com.instructure.pandautils.features.dashboard.notifications.DashboardRouter
6060
import com.instructure.pandautils.features.dashboard.widget.WidgetMetadata
6161
import com.instructure.pandautils.features.dashboard.widget.courseinvitation.CourseInvitationsWidget
62+
import com.instructure.pandautils.features.dashboard.widget.welcome.WelcomeWidget
6263
import com.instructure.pandautils.features.dashboard.widget.institutionalannouncements.InstitutionalAnnouncementsWidget
6364
import com.instructure.student.R
6465
import com.instructure.student.activity.NavigationActivity
65-
import com.instructure.student.features.dashboard.widget.welcome.WelcomeWidget
6666
import kotlinx.coroutines.flow.SharedFlow
6767

6868
@Composable

libs/pandares/src/main/res/values/strings.xml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,6 +2230,123 @@
22302230
<string name="courseInvitationDeclineConfirmTitle">Decline Invitation</string>
22312231
<string name="courseInvitationDeclineConfirmMessage">Are you sure you want to decline the invitation to %s?</string>
22322232

2233+
<!-- Welcome Widget -->
2234+
<!-- Greetings with name (%s is the student's first name) -->
2235+
<string name="welcomeGreetingMorningWithName">Good morning, %s!</string>
2236+
<string name="welcomeGreetingAfternoonWithName">Good afternoon, %s!</string>
2237+
<string name="welcomeGreetingEveningWithName">Good evening, %s!</string>
2238+
<string name="welcomeGreetingNightWithName">Good night, %s!</string>
2239+
2240+
<!-- Greetings without name (fallback when user has no first name) -->
2241+
<string name="welcomeGreetingMorning">Good morning!</string>
2242+
<string name="welcomeGreetingAfternoon">Good afternoon!</string>
2243+
<string name="welcomeGreetingEvening">Good evening!</string>
2244+
<string name="welcomeGreetingNight">Good night!</string>
2245+
2246+
<!-- Welcome Widget Motivational Messages -->
2247+
<string-array name="welcomeMessagesGeneric">
2248+
<item>You\'ve got this.</item>
2249+
<item>Keep going — you\'re stronger than you feel right now.</item>
2250+
<item>One step at a time is still progress.</item>
2251+
<item>Don\'t give up — future you will thank you.</item>
2252+
<item>You\'re capable of more than you think.</item>
2253+
<item>Even on tough days, you\'re moving forward.</item>
2254+
<item>Trust yourself — you\'ve done hard things before.</item>
2255+
<item>Progress, not perfection. You\'re doing great.</item>
2256+
<item>Hang in there — you\'re not alone in this.</item>
2257+
<item>You\'re learning, growing, and doing better than you realize.</item>
2258+
<item>It\'s okay to stumble — you\'re still on the right path.</item>
2259+
<item>Keep pushing — you\'re closer than you think.</item>
2260+
<item>Small wins count too.</item>
2261+
<item>Keep going — you\'re closer than you think.</item>
2262+
<item>It\'s okay to pause. Breaks are part of learning.</item>
2263+
<item>Trying is already a win.</item>
2264+
<item>Progress &gt; perfection.</item>
2265+
<item>Showing up matters more than you know.</item>
2266+
<item>You\'re building skills, even on slow days.</item>
2267+
<item>Don\'t forget to breathe — you\'re doing fine.</item>
2268+
<item>Your pace is the right pace.</item>
2269+
<item>Not everything needs to be figured out today.</item>
2270+
<item>You belong here.</item>
2271+
<item>Every effort you make adds up.</item>
2272+
<item>It\'s okay to start again — as many times as you need.</item>
2273+
<item>What feels hard now will feel easier later.</item>
2274+
<item>Keep showing up — that\'s what counts.</item>
2275+
<item>Small steps move big mountains.</item>
2276+
<item>Rest is part of progress too.</item>
2277+
<item>You\'re doing better than you realize.</item>
2278+
<item>Even slow progress is still progress.</item>
2279+
<item>The future isn\'t built in a day — but you\'re on the way.</item>
2280+
<item>One assignment, one moment, one step at a time.</item>
2281+
<item>You don\'t have to be perfect to make an impact.</item>
2282+
<item>Learning is messy — and that\'s normal.</item>
2283+
<item>Every try is growth, even if it doesn\'t feel like it.</item>
2284+
<item>You\'ve done hard things before — you can do this too.</item>
2285+
<item>Your effort matters, even if no one sees it.</item>
2286+
<item>It\'s okay to take things slow.</item>
2287+
<item>You\'re moving forward, even on quiet days.</item>
2288+
<item>The path doesn\'t need to be clear yet — keep walking.</item>
2289+
<item>You\'re stronger than you feel right now.</item>
2290+
<item>Big goals are built from small steps.</item>
2291+
<item>Keep going — future you will thank you.</item>
2292+
<item>Even messy progress is still progress.</item>
2293+
<item>You\'re not behind — you\'re on your path.</item>
2294+
<item>It\'s okay to learn as you go.</item>
2295+
<item>You\'re growing in ways you might not see yet.</item>
2296+
<item>Your effort today is an investment in tomorrow.</item>
2297+
</string-array>
2298+
2299+
<string-array name="welcomeMessagesMorning">
2300+
<item>Morning! You\'ve got this — one class, one step at a time.</item>
2301+
<item>Not feeling ready? That\'s normal. Just start where you are.</item>
2302+
<item>Coffee helps, but kindness to yourself works better.</item>
2303+
<item>Tech acting up? Happens to all of us — don\'t stress.</item>
2304+
<item>Today doesn\'t need to be perfect, just possible.</item>
2305+
<item>Good morning — today is a new chance to learn and grow.</item>
2306+
<item>Even small steps this morning move you closer to your goals.</item>
2307+
<item>Take a breath — you don\'t need to have everything figured out yet.</item>
2308+
<item>Technology can be tricky, but you\'re not alone in learning it.</item>
2309+
</string-array>
2310+
2311+
<string-array name="welcomeMessagesAfternoon">
2312+
<item>Halfway there — you\'ve already done more than you think.</item>
2313+
<item>Feeling stuck? Everyone hits walls, just don\'t stop climbing.</item>
2314+
<item>Jobs, grades, the future… no one has it all figured out yet.</item>
2315+
<item>Brain tired? Quick break = better focus later.</item>
2316+
<item>Ask for help. Seriously, no one\'s doing this solo.</item>
2317+
<item>You\'ve already made it this far today — that\'s something to be proud of.</item>
2318+
<item>Need a pause? Recharging is part of learning too.</item>
2319+
<item>It\'s okay if the path feels uncertain — skills build step by step.</item>
2320+
<item>Reach out if you\'re stuck — support is always closer than it feels.</item>
2321+
</string-array>
2322+
2323+
<string-array name="welcomeMessagesEvening">
2324+
<item>Made it through the day — that\'s a win in itself.</item>
2325+
<item>Missing people? Shoot someone a quick "hey" — it helps.</item>
2326+
<item>Even if today felt messy, you showed up. That matters.</item>
2327+
<item>Remember: no grade measures your worth.</item>
2328+
<item>Relax, laugh, or scroll guilt-free — you earned it.</item>
2329+
<item>Well done getting through the day — progress counts, even when it\'s quiet.</item>
2330+
<item>Missing friends or mentors? Connection can come in small moments too.</item>
2331+
<item>Evenings are for reflection — notice what you\'ve learned today, not just what\'s left to do.</item>
2332+
<item>Your effort matters more than perfection.</item>
2333+
</string-array>
2334+
2335+
<string-array name="welcomeMessagesNight">
2336+
<item>Still grinding? Respect — but don\'t forget sleep exists.</item>
2337+
<item>Tomorrow you\'ll thank yourself for resting tonight.</item>
2338+
<item>Anxiety gets louder at night — don\'t believe all its noise.</item>
2339+
<item>You\'re not behind, you\'re just on your path.</item>
2340+
<item>Close the laptop — your brain needs dreams too.</item>
2341+
<item>It\'s okay to rest — tomorrow is waiting with new opportunities.</item>
2342+
<item>Learning is a marathon, not a sprint. Be kind to yourself tonight.</item>
2343+
<item>If worries feel heavy, remember you don\'t have to carry them alone.</item>
2344+
<item>End the day knowing that trying is already an achievement.</item>
2345+
</string-array>
2346+
2347+
<!-- Accessibility -->
2348+
<string name="welcomeWidgetContentDescription">Welcome message: %1$s. %2$s</string>
2349+
22332350
<!-- Institutional Announcements -->
22342351
<string name="institutionalAnnouncementsTitle">Announcements (%d)</string>
22352352
</resources>

libs/pandautils/src/main/java/com/instructure/pandautils/di/ApplicationModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import org.threeten.bp.Clock
5252
import java.util.Locale
5353
import java.util.TimeZone
5454
import javax.inject.Singleton
55+
import kotlin.random.Random
5556

5657
/**
5758
* Module that provides all the application scope dependencies, that are not related to other module.
@@ -189,4 +190,9 @@ class ApplicationModule {
189190
fun provideFileCache(): FileCache {
190191
return FileCache
191192
}
193+
194+
@Provides
195+
fun provideRandom(): Random {
196+
return Random.Default
197+
}
192198
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (C) 2025 - present Instructure, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, version 3 of the License.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
18+
19+
enum class TimeOfDay {
20+
MORNING, // 4am - 12pm
21+
AFTERNOON, // 12pm - 5pm
22+
EVENING, // 5pm - 9pm
23+
NIGHT // 9pm - 4am
24+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2025 - present Instructure, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, version 3 of the License.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
18+
19+
class TimeOfDayCalculator(private val timeProvider: TimeProvider) {
20+
21+
fun getTimeOfDay(): TimeOfDay {
22+
val hour = timeProvider.getCurrentHourOfDay()
23+
return when {
24+
hour < 4 -> TimeOfDay.NIGHT
25+
hour < 12 -> TimeOfDay.MORNING
26+
hour < 17 -> TimeOfDay.AFTERNOON
27+
hour < 21 -> TimeOfDay.EVENING
28+
else -> TimeOfDay.NIGHT
29+
}
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (C) 2025 - present Instructure, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, version 3 of the License.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
18+
19+
import java.util.Calendar
20+
21+
interface TimeProvider {
22+
fun getCurrentHourOfDay(): Int
23+
}
24+
25+
class SystemTimeProvider : TimeProvider {
26+
override fun getCurrentHourOfDay(): Int {
27+
return Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
28+
}
29+
}
Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,27 @@
1414
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
*/
1616

17-
package com.instructure.student.features.dashboard.widget.welcome
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
1818

1919
import androidx.compose.foundation.layout.Column
2020
import androidx.compose.foundation.layout.fillMaxWidth
2121
import androidx.compose.foundation.layout.padding
2222
import androidx.compose.material.Text
2323
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.LaunchedEffect
2425
import androidx.compose.runtime.collectAsState
2526
import androidx.compose.runtime.getValue
2627
import androidx.compose.ui.Modifier
2728
import androidx.compose.ui.res.colorResource
29+
import androidx.compose.ui.res.stringResource
30+
import androidx.compose.ui.semantics.contentDescription
31+
import androidx.compose.ui.semantics.semantics
2832
import androidx.compose.ui.text.font.FontWeight
2933
import androidx.compose.ui.tooling.preview.Preview
3034
import androidx.compose.ui.unit.dp
3135
import androidx.compose.ui.unit.sp
3236
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
33-
import com.instructure.student.R
37+
import com.instructure.pandautils.R
3438
import kotlinx.coroutines.flow.SharedFlow
3539

3640
@Composable
@@ -41,6 +45,12 @@ fun WelcomeWidget(
4145
val viewModel: WelcomeWidgetViewModel = hiltViewModel()
4246
val uiState by viewModel.uiState.collectAsState()
4347

48+
LaunchedEffect(refreshSignal) {
49+
refreshSignal.collect {
50+
viewModel.refresh()
51+
}
52+
}
53+
4454
WelcomeContent(
4555
modifier = modifier,
4656
uiState = uiState
@@ -52,7 +62,17 @@ private fun WelcomeContent(
5262
modifier: Modifier = Modifier,
5363
uiState: WelcomeWidgetUiState
5464
) {
55-
Column(modifier = modifier.padding(horizontal = 16.dp)) {
65+
val contentDescriptionText = stringResource(
66+
R.string.welcomeWidgetContentDescription,
67+
uiState.greeting,
68+
uiState.message
69+
)
70+
71+
Column(
72+
modifier = modifier
73+
.padding(horizontal = 16.dp)
74+
.semantics { contentDescription = contentDescriptionText }
75+
) {
5676
Text(
5777
modifier = Modifier.fillMaxWidth(),
5878
text = uiState.greeting,
@@ -61,11 +81,15 @@ private fun WelcomeContent(
6181
color = colorResource(R.color.textDarkest),
6282
lineHeight = 29.sp
6383
)
64-
Text(modifier = Modifier.fillMaxWidth(),
84+
Text(
85+
modifier = Modifier
86+
.fillMaxWidth()
87+
.padding(top = 2.dp),
6588
text = uiState.message,
6689
fontSize = 14.sp,
6790
color = colorResource(R.color.textDarkest),
68-
lineHeight = 19.sp)
91+
lineHeight = 19.sp
92+
)
6993
}
7094
}
7195

@@ -75,8 +99,8 @@ private fun WelcomeContent(
7599
fun WelcomeContentPreview() {
76100
WelcomeContent(
77101
uiState = WelcomeWidgetUiState(
78-
greeting = "Welcome back, Student!",
79-
message = "Here's what's happening in your courses today."
102+
greeting = "Good morning, Riley!",
103+
message = "Every small step you take is progress. Keep going!"
80104
)
81105
)
82-
}
106+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
*/
1616

17-
package com.instructure.student.features.dashboard.widget.welcome
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
1818

1919
data class WelcomeWidgetUiState(
2020
val greeting: String = "",
2121
val message: String = ""
22-
)
22+
)
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
*/
1616

17-
package com.instructure.student.features.dashboard.widget.welcome
17+
package com.instructure.pandautils.features.dashboard.widget.welcome
1818

1919
import androidx.lifecycle.ViewModel
20+
import com.instructure.pandautils.features.dashboard.widget.welcome.usecase.GetWelcomeGreetingUseCase
21+
import com.instructure.pandautils.features.dashboard.widget.welcome.usecase.GetWelcomeMessageUseCase
2022
import dagger.hilt.android.lifecycle.HiltViewModel
2123
import kotlinx.coroutines.flow.MutableStateFlow
2224
import kotlinx.coroutines.flow.StateFlow
@@ -25,17 +27,28 @@ import kotlinx.coroutines.flow.update
2527
import javax.inject.Inject
2628

2729
@HiltViewModel
28-
class WelcomeWidgetViewModel @Inject constructor() : ViewModel() {
30+
class WelcomeWidgetViewModel @Inject constructor(
31+
private val getWelcomeGreetingUseCase: GetWelcomeGreetingUseCase,
32+
private val getWelcomeMessageUseCase: GetWelcomeMessageUseCase
33+
) : ViewModel() {
2934

3035
private val _uiState = MutableStateFlow(WelcomeWidgetUiState())
3136
val uiState: StateFlow<WelcomeWidgetUiState> = _uiState.asStateFlow()
3237

3338
init {
39+
loadWelcomeContent()
40+
}
41+
42+
fun refresh() {
43+
loadWelcomeContent()
44+
}
45+
46+
private fun loadWelcomeContent() {
3447
_uiState.update {
3548
it.copy(
36-
greeting = "Welcome back, Learner!",
37-
message = "Here you can find an overview of your courses and activities."
49+
greeting = getWelcomeGreetingUseCase(),
50+
message = getWelcomeMessageUseCase()
3851
)
3952
}
4053
}
41-
}
54+
}

0 commit comments

Comments
 (0)