Skip to content

Commit 78be19e

Browse files
committed
Redesign dashboard header and last-course UX
Redesign the Bell/dashboard header and add logic to surface the last-visited course. - Add lastVisitedCourseId/title prefs to SharedPrefManager with getters/setters and save them when opening a course in TakeCourseFragment. - BellDashboardViewModel: inject SharedPrefManager, compute LastVisitedCourseInfo (title, progress %) and expose it as a StateFlow; add loadLastVisitedCourse. - BellDashboardFragment: observe lastVisitedCourse, show a "continue" card that opens TakeCourseFragment, set greeting by time of day, hide badge container when empty, and move network states to a sync chip with updateSyncChip helper. - UI: replace card_profile_bell layouts (various landscape sizes) with a redesigned header layout (avatar, greeting, name/role, badges, sync chip); remove home_offline_banner include and layout; add small drawables (bg_chip_connecting, dot_connecting) and update color/strings resources for new states and labels. These changes improve visibility of connection state and make it easy for users to resume recently-viewed courses.
1 parent bfdff2b commit 78be19e

20 files changed

Lines changed: 627 additions & 389 deletions

File tree

app/src/main/java/org/ole/planet/myplanet/services/SharedPrefManager.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class SharedPrefManager @Inject constructor(@ApplicationContext private val cont
6363
private const val KEY_NOTIFICATION_SHOWN = "notification_shown"
6464
private const val VERSION_DETAIL = "versionDetail"
6565
private const val CONCATENATED_LINKS = "concatenated_links"
66+
private const val LAST_VISITED_COURSE_ID = "lastVisitedCourseId"
67+
private const val LAST_VISITED_COURSE_TITLE = "lastVisitedCourseTitle"
6668
}
6769

6870
enum class SyncKey(val key: String) {
@@ -297,6 +299,12 @@ class SharedPrefManager @Inject constructor(@ApplicationContext private val cont
297299
fun getConcatenatedLinks(): String? = pref.getString(CONCATENATED_LINKS, null)
298300
fun setConcatenatedLinks(json: String) = pref.edit { putString(CONCATENATED_LINKS, json) }
299301

302+
fun getLastVisitedCourseId(): String? = pref.getString(LAST_VISITED_COURSE_ID, null)
303+
fun setLastVisitedCourseId(id: String?) = pref.edit { putString(LAST_VISITED_COURSE_ID, id) }
304+
305+
fun getLastVisitedCourseTitle(): String? = pref.getString(LAST_VISITED_COURSE_TITLE, null)
306+
fun setLastVisitedCourseTitle(title: String?) = pref.edit { putString(LAST_VISITED_COURSE_TITLE, title) }
307+
300308
fun getRawString(key: String, default: String = ""): String = pref.getString(key, default) ?: default
301309
fun setRawString(key: String, value: String) = pref.edit { putString(key, value) }
302310
fun getRawLong(key: String, default: Long = 0L): Long = pref.getLong(key, default)

app/src/main/java/org/ole/planet/myplanet/ui/courses/TakeCourseFragment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class TakeCourseFragment : Fragment(), ViewPager.OnPageChangeListener, View.OnCl
3838
lateinit var userSessionManager: UserSessionManager
3939
@Inject
4040
lateinit var coursesRepository: CoursesRepository
41+
@Inject
42+
lateinit var sharedPrefManager: org.ole.planet.myplanet.services.SharedPrefManager
4143
private var currentCourse: RealmMyCourse? = null
4244
lateinit var steps: List<RealmCourseStep?>
4345
var position = 0
@@ -78,6 +80,8 @@ class TakeCourseFragment : Fragment(), ViewPager.OnPageChangeListener, View.OnCl
7880
}
7981
binding.contentLayout.visibility = View.VISIBLE
8082
currentCourse = course
83+
sharedPrefManager.setLastVisitedCourseId(course.courseId)
84+
sharedPrefManager.setLastVisitedCourseTitle(course.courseTitle)
8185
binding.tvCourseTitle.text = currentCourse?.courseTitle
8286

8387
steps = coursesRepository.getCourseSteps(courseId!!)

app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardFragment.kt

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,18 @@ class BellDashboardFragment : BaseDashboardFragment() {
8888
observeLibraryPreviewItems()
8989
observeTeamPreviewItems()
9090
observeLifePreviewItems()
91+
observeLastVisitedCourse()
92+
setGreetingBasedOnTime()
9193
viewLifecycleOwner.lifecycleScope.launch {
9294
val wasUserNull = user == null
9395
user = profileDbHandler.getUserModel()
94-
binding.cardProfileBell.txtCommunityName.text = user?.planetCode
9596
user?.id?.let {
9697
viewModel.loadCompletedCourses(it)
9798
viewModel.loadCoursePreviewItems(it)
9899
viewModel.loadLibraryPreviewItems(it)
99100
viewModel.loadTeamPreviewItems(it)
100101
viewModel.loadLifePreviewItems(it)
102+
viewModel.loadLastVisitedCourse(it)
101103
}
102104
if (wasUserNull && (user?.id?.startsWith("guest") != true) && !DashboardActivity.isFromNotificationAction) {
103105
checkPendingSurveys()
@@ -159,12 +161,28 @@ class BellDashboardFragment : BaseDashboardFragment() {
159161
context ?: return
160162

161163
when (status) {
162-
is NetworkStatus.Disconnected -> setNetworkIndicatorColor(R.color.md_red_700)
163-
is NetworkStatus.Connecting -> handleConnectingState()
164-
is NetworkStatus.Connected -> setNetworkIndicatorColor(R.color.green)
164+
is NetworkStatus.Disconnected -> {
165+
setNetworkIndicatorColor(R.color.md_red_700)
166+
updateSyncChip(R.drawable.bg_chip_offline, R.drawable.dot_offline, R.string.offline, R.color.chip_offline_ink)
167+
}
168+
is NetworkStatus.Connecting -> {
169+
handleConnectingState()
170+
updateSyncChip(R.drawable.bg_chip_connecting, R.drawable.dot_connecting, R.string.connecting, R.color.chip_connecting_ink)
171+
}
172+
is NetworkStatus.Connected -> {
173+
setNetworkIndicatorColor(R.color.green)
174+
updateSyncChip(R.drawable.bg_chip_synced, R.drawable.dot_synced, R.string.online, R.color.chip_synced_ink)
175+
}
165176
}
166177
}
167178

179+
private fun updateSyncChip(bgRes: Int, dotRes: Int, labelRes: Int, textColorRes: Int) {
180+
binding.cardProfileBell.chipSync?.setBackgroundResource(bgRes)
181+
binding.cardProfileBell.dotSync?.setBackgroundResource(dotRes)
182+
binding.cardProfileBell.txtSyncState?.text = getString(labelRes)
183+
binding.cardProfileBell.txtSyncState?.setTextColor(ContextCompat.getColor(requireContext(), textColorRes))
184+
}
185+
168186
private fun checkPendingSurveys() {
169187
viewLifecycleOwner.lifecycleScope.launch {
170188
if (checkScheduledReminders()) {
@@ -389,6 +407,11 @@ class BellDashboardFragment : BaseDashboardFragment() {
389407

390408
private fun showBadges(completedCourses: List<CourseCompletion>) {
391409
binding.cardProfileBell.llBadges.removeAllViews()
410+
if (completedCourses.isEmpty()) {
411+
binding.cardProfileBell.llBadges.visibility = View.GONE
412+
return
413+
}
414+
binding.cardProfileBell.llBadges.visibility = View.VISIBLE
392415
completedCourses.forEachIndexed { index, course ->
393416
val rootView = requireActivity().findViewById<ViewGroup>(android.R.id.content)
394417
val star = LayoutInflater.from(activity).inflate(R.layout.image_start, rootView, false) as ImageView
@@ -626,6 +649,37 @@ class BellDashboardFragment : BaseDashboardFragment() {
626649
openBtn.setOnClickListener { onOpenAll() }
627650
}
628651

652+
private fun observeLastVisitedCourse() {
653+
binding.homeContinue.root.visibility = View.GONE
654+
binding.txtYourLearning.visibility = View.GONE
655+
binding.dashTitle.visibility = View.GONE
656+
viewLifecycleOwner.lifecycleScope.launch {
657+
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
658+
viewModel.lastVisitedCourse.collectLatest { info ->
659+
val visible = if (info == null) View.GONE else View.VISIBLE
660+
binding.homeContinue.root.visibility = visible
661+
binding.txtYourLearning.visibility = visible
662+
binding.dashTitle.visibility = visible
663+
if (info == null) return@collectLatest
664+
binding.homeContinue.txtContinueTitle.text = info.courseTitle
665+
val metaText = when {
666+
info.progressPercent >= 100 -> getString(R.string.course_completed_label)
667+
info.progressPercent > 0 -> "${info.progressPercent}% ${getString(R.string.complete_label)}"
668+
else -> getString(R.string.not_started_label)
669+
}
670+
binding.homeContinue.txtContinueMeta.text = metaText
671+
binding.homeContinue.progressContinue.progress = info.progressPercent
672+
binding.homeContinue.txtContinuePercent.text = "${info.progressPercent}%"
673+
binding.homeContinue.root.setOnClickListener {
674+
val f = TakeCourseFragment()
675+
f.arguments = Bundle().apply { putString("id", info.courseId) }
676+
homeItemClickListener?.openCallFragment(f)
677+
}
678+
}
679+
}
680+
}
681+
}
682+
629683
private fun observeCoursePreviewItems() {
630684
viewLifecycleOwner.lifecycleScope.launch {
631685
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -687,6 +741,16 @@ class BellDashboardFragment : BaseDashboardFragment() {
687741
)
688742
}
689743

744+
private fun setGreetingBasedOnTime() {
745+
val timeOfDay = java.util.Calendar.getInstance().get(java.util.Calendar.HOUR_OF_DAY)
746+
binding.cardProfileBell.txtGreeting?.text = when {
747+
timeOfDay < 12 -> getString(R.string.good_morning)
748+
timeOfDay < 16 -> getString(R.string.good_afternoon)
749+
timeOfDay < 21 -> getString(R.string.good_evening)
750+
else -> getString(R.string.good_night)
751+
}
752+
}
753+
690754
private fun declareElements() {
691755
// Library, Courses, Teams, MyLife header clicks are wired in their observe* methods
692756
binding.fabMyActivity.setOnClickListener { openHelperFragment(ActivitiesFragment()) }

app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardViewModel.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.ole.planet.myplanet.repository.LifeRepository
1515
import org.ole.planet.myplanet.repository.ProgressRepository
1616
import org.ole.planet.myplanet.repository.ResourcesRepository
1717
import org.ole.planet.myplanet.repository.TeamsRepository
18+
import org.ole.planet.myplanet.services.SharedPrefManager
1819
import org.ole.planet.myplanet.utils.DispatcherProvider
1920
import org.ole.planet.myplanet.utils.NetworkUtils.isNetworkConnectedFlow
2021

@@ -25,6 +26,7 @@ class BellDashboardViewModel @Inject constructor(
2526
private val teamsRepository: TeamsRepository,
2627
private val resourcesRepository: ResourcesRepository,
2728
private val lifeRepository: LifeRepository,
29+
private val sharedPrefManager: SharedPrefManager,
2830
private val dispatcherProvider: DispatcherProvider
2931
) : ViewModel() {
3032
private val _networkStatus = MutableStateFlow<NetworkStatus>(NetworkStatus.Disconnected)
@@ -45,6 +47,9 @@ class BellDashboardViewModel @Inject constructor(
4547
private val _lifePreviewItems = MutableStateFlow<List<LifePreviewItem>>(emptyList())
4648
val lifePreviewItems: StateFlow<List<LifePreviewItem>> = _lifePreviewItems.asStateFlow()
4749

50+
private val _lastVisitedCourse = MutableStateFlow<LastVisitedCourseInfo?>(null)
51+
val lastVisitedCourse: StateFlow<LastVisitedCourseInfo?> = _lastVisitedCourse.asStateFlow()
52+
4853
init {
4954
viewModelScope.launch {
5055
isNetworkConnectedFlow.collect { isConnected ->
@@ -128,6 +133,30 @@ class BellDashboardViewModel @Inject constructor(
128133
}
129134
}
130135

136+
fun loadLastVisitedCourse(userId: String) {
137+
viewModelScope.launch {
138+
val info = withContext(dispatcherProvider.io) {
139+
val courseId = sharedPrefManager.getLastVisitedCourseId() ?: return@withContext null
140+
val course = coursesRepository.getCourseById(courseId) ?: return@withContext null
141+
val allProgress = progressRepository.getProgressRecords(userId)
142+
val passedSteps = allProgress
143+
.filter { it.courseId == courseId && it.passed }
144+
.map { it.stepNum }
145+
.toSet()
146+
.size
147+
val totalSteps = course.courseSteps?.size ?: 0
148+
val percent = if (totalSteps > 0) (passedSteps * 100) / totalSteps else 0
149+
LastVisitedCourseInfo(
150+
courseId = courseId,
151+
courseTitle = course.courseTitle ?: sharedPrefManager.getLastVisitedCourseTitle() ?: "",
152+
progressPercent = percent,
153+
totalSteps = totalSteps
154+
)
155+
}
156+
_lastVisitedCourse.value = info
157+
}
158+
}
159+
131160
fun loadCompletedCourses(userId: String) {
132161
viewModelScope.launch {
133162
val completedCourses = withContext(dispatcherProvider.io) {
@@ -191,6 +220,12 @@ data class CoursePreviewItem(
191220
data class LibraryPreviewItem(val resourceId: String, val title: String, val subline: String)
192221
data class TeamPreviewItem(val teamId: String, val name: String, val teamType: String)
193222
data class LifePreviewItem(val imageId: String, val title: String)
223+
data class LastVisitedCourseInfo(
224+
val courseId: String,
225+
val courseTitle: String,
226+
val progressPercent: Int,
227+
val totalSteps: Int
228+
)
194229

195230
sealed class NetworkStatus {
196231
object Disconnected : NetworkStatus()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
3+
<solid android:color="@color/chip_connecting_bg" />
4+
<corners android:radius="100dp" />
5+
</shape>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
3+
<solid android:color="@color/md_amber_700" />
4+
</shape>

0 commit comments

Comments
 (0)