Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ import com.instructure.pandautils.compose.composables.Loading
import com.instructure.pandautils.features.dashboard.notifications.DashboardRouter
import com.instructure.pandautils.features.dashboard.widget.WidgetMetadata
import com.instructure.pandautils.features.dashboard.widget.courseinvitation.CourseInvitationsWidget
import com.instructure.pandautils.features.dashboard.widget.welcome.WelcomeWidget
import com.instructure.pandautils.features.dashboard.widget.institutionalannouncements.InstitutionalAnnouncementsWidget
import com.instructure.student.R
import com.instructure.student.activity.NavigationActivity
import com.instructure.student.features.dashboard.widget.welcome.WelcomeWidget
import kotlinx.coroutines.flow.SharedFlow

@Composable
Expand Down
117 changes: 117 additions & 0 deletions libs/pandares/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,123 @@
<string name="courseInvitationDeclineConfirmTitle">Decline Invitation</string>
<string name="courseInvitationDeclineConfirmMessage">Are you sure you want to decline the invitation to %s?</string>

<!-- Welcome Widget -->
<!-- Greetings with name (%s is the student's first name) -->
<string name="welcomeGreetingMorningWithName">Good morning, %s!</string>
<string name="welcomeGreetingAfternoonWithName">Good afternoon, %s!</string>
<string name="welcomeGreetingEveningWithName">Good evening, %s!</string>
<string name="welcomeGreetingNightWithName">Good night, %s!</string>

<!-- Greetings without name (fallback when user has no first name) -->
<string name="welcomeGreetingMorning">Good morning!</string>
<string name="welcomeGreetingAfternoon">Good afternoon!</string>
<string name="welcomeGreetingEvening">Good evening!</string>
<string name="welcomeGreetingNight">Good night!</string>

<!-- Welcome Widget Motivational Messages -->
<string-array name="welcomeMessagesGeneric">
<item>You\'ve got this.</item>
<item>Keep going — you\'re stronger than you feel right now.</item>
<item>One step at a time is still progress.</item>
<item>Don\'t give up — future you will thank you.</item>
<item>You\'re capable of more than you think.</item>
<item>Even on tough days, you\'re moving forward.</item>
<item>Trust yourself — you\'ve done hard things before.</item>
<item>Progress, not perfection. You\'re doing great.</item>
<item>Hang in there — you\'re not alone in this.</item>
<item>You\'re learning, growing, and doing better than you realize.</item>
<item>It\'s okay to stumble — you\'re still on the right path.</item>
<item>Keep pushing — you\'re closer than you think.</item>
<item>Small wins count too.</item>
<item>Keep going — you\'re closer than you think.</item>
<item>It\'s okay to pause. Breaks are part of learning.</item>
<item>Trying is already a win.</item>
<item>Progress &gt; perfection.</item>
<item>Showing up matters more than you know.</item>
<item>You\'re building skills, even on slow days.</item>
<item>Don\'t forget to breathe — you\'re doing fine.</item>
<item>Your pace is the right pace.</item>
<item>Not everything needs to be figured out today.</item>
<item>You belong here.</item>
<item>Every effort you make adds up.</item>
<item>It\'s okay to start again — as many times as you need.</item>
<item>What feels hard now will feel easier later.</item>
<item>Keep showing up — that\'s what counts.</item>
<item>Small steps move big mountains.</item>
<item>Rest is part of progress too.</item>
<item>You\'re doing better than you realize.</item>
<item>Even slow progress is still progress.</item>
<item>The future isn\'t built in a day — but you\'re on the way.</item>
<item>One assignment, one moment, one step at a time.</item>
<item>You don\'t have to be perfect to make an impact.</item>
<item>Learning is messy — and that\'s normal.</item>
<item>Every try is growth, even if it doesn\'t feel like it.</item>
<item>You\'ve done hard things before — you can do this too.</item>
<item>Your effort matters, even if no one sees it.</item>
<item>It\'s okay to take things slow.</item>
<item>You\'re moving forward, even on quiet days.</item>
<item>The path doesn\'t need to be clear yet — keep walking.</item>
<item>You\'re stronger than you feel right now.</item>
<item>Big goals are built from small steps.</item>
<item>Keep going — future you will thank you.</item>
<item>Even messy progress is still progress.</item>
<item>You\'re not behind — you\'re on your path.</item>
<item>It\'s okay to learn as you go.</item>
<item>You\'re growing in ways you might not see yet.</item>
<item>Your effort today is an investment in tomorrow.</item>
</string-array>

<string-array name="welcomeMessagesMorning">
<item>Morning! You\'ve got this — one class, one step at a time.</item>
<item>Not feeling ready? That\'s normal. Just start where you are.</item>
<item>Coffee helps, but kindness to yourself works better.</item>
<item>Tech acting up? Happens to all of us — don\'t stress.</item>
<item>Today doesn\'t need to be perfect, just possible.</item>
<item>Good morning — today is a new chance to learn and grow.</item>
<item>Even small steps this morning move you closer to your goals.</item>
<item>Take a breath — you don\'t need to have everything figured out yet.</item>
<item>Technology can be tricky, but you\'re not alone in learning it.</item>
</string-array>

<string-array name="welcomeMessagesAfternoon">
<item>Halfway there — you\'ve already done more than you think.</item>
<item>Feeling stuck? Everyone hits walls, just don\'t stop climbing.</item>
<item>Jobs, grades, the future… no one has it all figured out yet.</item>
<item>Brain tired? Quick break = better focus later.</item>
<item>Ask for help. Seriously, no one\'s doing this solo.</item>
<item>You\'ve already made it this far today — that\'s something to be proud of.</item>
<item>Need a pause? Recharging is part of learning too.</item>
<item>It\'s okay if the path feels uncertain — skills build step by step.</item>
<item>Reach out if you\'re stuck — support is always closer than it feels.</item>
</string-array>

<string-array name="welcomeMessagesEvening">
<item>Made it through the day — that\'s a win in itself.</item>
<item>Missing people? Shoot someone a quick "hey" — it helps.</item>
<item>Even if today felt messy, you showed up. That matters.</item>
<item>Remember: no grade measures your worth.</item>
<item>Relax, laugh, or scroll guilt-free — you earned it.</item>
<item>Well done getting through the day — progress counts, even when it\'s quiet.</item>
<item>Missing friends or mentors? Connection can come in small moments too.</item>
<item>Evenings are for reflection — notice what you\'ve learned today, not just what\'s left to do.</item>
<item>Your effort matters more than perfection.</item>
</string-array>

<string-array name="welcomeMessagesNight">
<item>Still grinding? Respect — but don\'t forget sleep exists.</item>
<item>Tomorrow you\'ll thank yourself for resting tonight.</item>
<item>Anxiety gets louder at night — don\'t believe all its noise.</item>
<item>You\'re not behind, you\'re just on your path.</item>
<item>Close the laptop — your brain needs dreams too.</item>
<item>It\'s okay to rest — tomorrow is waiting with new opportunities.</item>
<item>Learning is a marathon, not a sprint. Be kind to yourself tonight.</item>
<item>If worries feel heavy, remember you don\'t have to carry them alone.</item>
<item>End the day knowing that trying is already an achievement.</item>
</string-array>

<!-- Accessibility -->
<string name="welcomeWidgetContentDescription">Welcome message: %1$s. %2$s</string>

<!-- Institutional Announcements -->
<string name="institutionalAnnouncementsTitle">Announcements (%d)</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import org.threeten.bp.Clock
import java.util.Locale
import java.util.TimeZone
import javax.inject.Singleton
import kotlin.random.Random

/**
* Module that provides all the application scope dependencies, that are not related to other module.
Expand Down Expand Up @@ -189,4 +190,9 @@ class ApplicationModule {
fun provideFileCache(): FileCache {
return FileCache
}

@Provides
fun provideRandom(): Random {
return Random.Default
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2025 - present Instructure, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.pandautils.features.dashboard.widget.welcome

enum class TimeOfDay {
MORNING, // 4am - 12pm
AFTERNOON, // 12pm - 5pm
EVENING, // 5pm - 9pm
NIGHT // 9pm - 4am
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2025 - present Instructure, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.pandautils.features.dashboard.widget.welcome

class TimeOfDayCalculator(private val timeProvider: TimeProvider) {

fun getTimeOfDay(): TimeOfDay {
val hour = timeProvider.getCurrentHourOfDay()
return when {
hour < 4 -> TimeOfDay.NIGHT
hour < 12 -> TimeOfDay.MORNING
hour < 17 -> TimeOfDay.AFTERNOON
hour < 21 -> TimeOfDay.EVENING
else -> TimeOfDay.NIGHT
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2025 - present Instructure, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.pandautils.features.dashboard.widget.welcome

import java.util.Calendar

interface TimeProvider {
fun getCurrentHourOfDay(): Int
}

class SystemTimeProvider : TimeProvider {
override fun getCurrentHourOfDay(): Int {
return Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.student.features.dashboard.widget.welcome
package com.instructure.pandautils.features.dashboard.widget.welcome

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.instructure.student.R
import com.instructure.pandautils.R
import kotlinx.coroutines.flow.SharedFlow

@Composable
Expand All @@ -41,6 +45,12 @@ fun WelcomeWidget(
val viewModel: WelcomeWidgetViewModel = hiltViewModel()
val uiState by viewModel.uiState.collectAsState()

LaunchedEffect(refreshSignal) {
refreshSignal.collect {
viewModel.refresh()
}
}

WelcomeContent(
modifier = modifier,
uiState = uiState
Expand All @@ -52,7 +62,17 @@ private fun WelcomeContent(
modifier: Modifier = Modifier,
uiState: WelcomeWidgetUiState
) {
Column(modifier = modifier.padding(horizontal = 16.dp)) {
val contentDescriptionText = stringResource(
R.string.welcomeWidgetContentDescription,
uiState.greeting,
uiState.message
)

Column(
modifier = modifier
.padding(horizontal = 16.dp)
.semantics { contentDescription = contentDescriptionText }
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = uiState.greeting,
Expand All @@ -61,11 +81,15 @@ private fun WelcomeContent(
color = colorResource(R.color.textDarkest),
lineHeight = 29.sp
)
Text(modifier = Modifier.fillMaxWidth(),
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 2.dp),
text = uiState.message,
fontSize = 14.sp,
color = colorResource(R.color.textDarkest),
lineHeight = 19.sp)
lineHeight = 19.sp
)
}
}

Expand All @@ -75,8 +99,8 @@ private fun WelcomeContent(
fun WelcomeContentPreview() {
WelcomeContent(
uiState = WelcomeWidgetUiState(
greeting = "Welcome back, Student!",
message = "Here's what's happening in your courses today."
greeting = "Good morning, Riley!",
message = "Every small step you take is progress. Keep going!"
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.student.features.dashboard.widget.welcome
package com.instructure.pandautils.features.dashboard.widget.welcome

data class WelcomeWidgetUiState(
val greeting: String = "",
val message: String = ""
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.instructure.student.features.dashboard.widget.welcome
package com.instructure.pandautils.features.dashboard.widget.welcome

import androidx.lifecycle.ViewModel
import com.instructure.pandautils.features.dashboard.widget.welcome.usecase.GetWelcomeGreetingUseCase
import com.instructure.pandautils.features.dashboard.widget.welcome.usecase.GetWelcomeMessageUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -25,17 +27,28 @@ import kotlinx.coroutines.flow.update
import javax.inject.Inject

@HiltViewModel
class WelcomeWidgetViewModel @Inject constructor() : ViewModel() {
class WelcomeWidgetViewModel @Inject constructor(
private val getWelcomeGreetingUseCase: GetWelcomeGreetingUseCase,
private val getWelcomeMessageUseCase: GetWelcomeMessageUseCase
) : ViewModel() {

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

init {
loadWelcomeContent()
}

fun refresh() {
loadWelcomeContent()
}

private fun loadWelcomeContent() {
_uiState.update {
it.copy(
greeting = "Welcome back, Learner!",
message = "Here you can find an overview of your courses and activities."
greeting = getWelcomeGreetingUseCase(),
message = getWelcomeMessageUseCase()
)
}
}
}
}
Loading
Loading