From fc95cfc1242a2f97006e74914dcd52e8708e58e4 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk Date: Thu, 23 May 2024 18:16:24 +0300 Subject: [PATCH] fix: Fixes according to PR feedback --- .../main/java/org/openedx/app/AppRouter.kt | 6 +- .../main/java/org/openedx/app/MainFragment.kt | 10 +- .../java/org/openedx/app/MainViewModel.kt | 7 +- .../java/org/openedx/app/di/ScreenModule.kt | 8 +- app/src/main/res/color/bottom_nav_color.xml | 6 +- app/src/main/res/drawable/app_ic_rows.xml | 8 +- app/src/main/res/layout/fragment_main.xml | 2 +- app/src/main/res/menu/bottom_view_menu.xml | 16 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../core/data/model/CourseAssignments.kt | 8 +- .../core/data/model/CourseDateBlock.kt | 56 ++--- .../room/discovery/EnrolledCourseEntity.kt | 6 +- .../openedx/core/domain/model/CourseStatus.kt | 2 +- .../org/openedx/core/domain/model/Progress.kt | 2 +- .../org/openedx/core/module/DownloadWorker.kt | 7 +- .../openedx/core/module/TranscriptManager.kt | 3 +- .../global/InDevelopmentScreen.kt | 31 --- .../java/org/openedx/core/ui/ComposeCommon.kt | 6 +- .../java/org/openedx/core/utils/FileUtil.kt | 2 +- .../main/res/drawable/core_ic_settings.xml | 20 -- .../res/drawable/ic_core_chapter_icon.xml | 49 +++-- core/src/main/res/values-night/colors.xml | 2 +- core/src/main/res/values/colors.xml | 2 +- .../container/CourseContainerFragment.kt | 8 +- .../container/CourseContainerViewModel.kt | 6 +- .../dates/CourseDatesViewModel.kt | 4 +- .../outline/CourseOutlineScreen.kt | 16 +- .../outline/CourseOutlineViewModel.kt | 8 +- .../course/presentation/ui/CourseVideosUI.kt | 14 +- .../AllEnrolledCoursesFragment.kt | 194 +----------------- .../presentation/AllEnrolledCoursesView.kt | 194 ++++++++++++++++++ .../AllEnrolledCoursesViewModel.kt | 26 ++- .../presentation/PrimaryCourseFragment.kt | 2 +- ...ryCourseScreen.kt => PrimaryCourseView.kt} | 50 ++--- .../presentation/PrimaryCourseViewModel.kt | 23 ++- .../data/repository/DashboardRepository.kt | 3 +- ...rdFragment.kt => DashboardListFragment.kt} | 2 +- ...ViewModel.kt => DashboardListViewModel.kt} | 2 +- .../dashboard/presentation/DashboardRouter.kt | 2 +- .../learn/presentation/LearnFragment.kt | 3 - .../main/res/drawable/dashboard_ic_book.xml | 30 +-- dashboard/src/main/res/values/strings.xml | 3 +- .../presentation/DashboardViewModelTest.kt | 20 +- 44 files changed, 429 insertions(+), 444 deletions(-) delete mode 100644 core/src/main/java/org/openedx/core/presentation/global/InDevelopmentScreen.kt delete mode 100644 core/src/main/res/drawable/core_ic_settings.xml create mode 100644 dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt rename dashboard/src/main/java/org/openedx/courses/presentation/{PrimaryCourseScreen.kt => PrimaryCourseView.kt} (94%) rename dashboard/src/main/java/org/openedx/dashboard/presentation/{ListDashboardFragment.kt => DashboardListFragment.kt} (99%) rename dashboard/src/main/java/org/openedx/dashboard/presentation/{ListDashboardViewModel.kt => DashboardListViewModel.kt} (99%) diff --git a/app/src/main/java/org/openedx/app/AppRouter.kt b/app/src/main/java/org/openedx/app/AppRouter.kt index 277db69ad..bc5dbdda4 100644 --- a/app/src/main/java/org/openedx/app/AppRouter.kt +++ b/app/src/main/java/org/openedx/app/AppRouter.kt @@ -160,16 +160,16 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di courseTitle: String, enrollmentMode: String, openDates: Boolean, - openBlock: String + resumeBlockId: String ) { replaceFragmentWithBackStack( fm, - CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode, openDates, openBlock) + CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode, openDates, resumeBlockId) ) } override fun navigateToEnrolledProgramInfo(fm: FragmentManager, pathId: String) { - replaceFragmentWithBackStack(fm, ProgramFragment(true)) + replaceFragmentWithBackStack(fm, ProgramFragment.newInstance(pathId)) } override fun navigateToNoAccess( diff --git a/app/src/main/java/org/openedx/app/MainFragment.kt b/app/src/main/java/org/openedx/app/MainFragment.kt index c1932ece0..c679a0a5b 100644 --- a/app/src/main/java/org/openedx/app/MainFragment.kt +++ b/app/src/main/java/org/openedx/app/MainFragment.kt @@ -13,7 +13,6 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.openedx.app.databinding.FragmentMainBinding import org.openedx.core.adapter.NavigationFragmentAdapter -import org.openedx.core.config.Config import org.openedx.core.config.DashboardConfig import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment import org.openedx.core.presentation.global.viewBinding @@ -28,7 +27,6 @@ class MainFragment : Fragment(R.layout.fragment_main) { private val binding by viewBinding(FragmentMainBinding::bind) private val viewModel by viewModel() private val router by inject() - private val config by inject() private lateinit var adapter: NavigationFragmentAdapter @@ -53,7 +51,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { binding.viewPager.setCurrentItem(0, false) } - R.id.fragmentHome -> { + R.id.fragmentDiscover -> { viewModel.logDiscoveryTabClickedEvent() binding.viewPager.setCurrentItem(1, false) } @@ -75,7 +73,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { viewLifecycleOwner.lifecycleScope.launch { viewModel.navigateToDiscovery.collect { shouldNavigateToDiscovery -> if (shouldNavigateToDiscovery) { - binding.bottomNavView.selectedItemId = R.id.fragmentHome + binding.bottomNavView.selectedItemId = R.id.fragmentDiscover } } } @@ -84,7 +82,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId -> val infoType = getString(ARG_INFO_TYPE) - if (config.getDiscoveryConfig().isViewTypeWebView() && infoType != null) { + if (viewModel.isDiscoveryTypeWebView && infoType != null) { router.navigateToCourseInfo(parentFragmentManager, courseId, infoType) } else { router.navigateToCourseDetail(parentFragmentManager, courseId) @@ -102,7 +100,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { binding.viewPager.offscreenPageLimit = 4 val discoveryFragment = DiscoveryNavigator(viewModel.isDiscoveryTypeWebView).getDiscoveryFragment() - val dashboardFragment = when (config.getDashboardConfig().getType()) { + val dashboardFragment = when (viewModel.dashboardType) { DashboardConfig.DashboardType.LIST -> ListDashboardFragment() DashboardConfig.DashboardType.PRIMARY_COURSE -> LearnFragment() } diff --git a/app/src/main/java/org/openedx/app/MainViewModel.kt b/app/src/main/java/org/openedx/app/MainViewModel.kt index da681e8e1..eed901039 100644 --- a/app/src/main/java/org/openedx/app/MainViewModel.kt +++ b/app/src/main/java/org/openedx/app/MainViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -31,8 +30,8 @@ class MainViewModel( get() = _navigateToDiscovery.asSharedFlow() val isDiscoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView() + val dashboardType get() = config.getDashboardConfig().getType() - @OptIn(FlowPreview::class) override fun onCreate(owner: LifecycleOwner) { super.onCreate(owner) notifier.notifier @@ -57,10 +56,6 @@ class MainViewModel( logEvent(AppAnalyticsEvent.MY_COURSES) } - fun logMyProgramsTabClickedEvent() { - logEvent(AppAnalyticsEvent.MY_PROGRAMS) - } - fun logProfileTabClickedEvent() { logEvent(AppAnalyticsEvent.PROFILE) } diff --git a/app/src/main/java/org/openedx/app/di/ScreenModule.kt b/app/src/main/java/org/openedx/app/di/ScreenModule.kt index 204f49bad..5e7f553ad 100644 --- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt +++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt @@ -33,7 +33,7 @@ import org.openedx.courses.presentation.AllEnrolledCoursesViewModel import org.openedx.courses.presentation.PrimaryCourseViewModel import org.openedx.dashboard.data.repository.DashboardRepository import org.openedx.dashboard.domain.interactor.DashboardInteractor -import org.openedx.dashboard.presentation.ListDashboardViewModel +import org.openedx.dashboard.presentation.DashboardListViewModel import org.openedx.discovery.data.repository.DiscoveryRepository import org.openedx.discovery.domain.interactor.DiscoveryInteractor import org.openedx.discovery.presentation.NativeDiscoveryViewModel @@ -119,7 +119,7 @@ val screenModule = module { factory { DashboardRepository(get(), get(), get(), get()) } factory { DashboardInteractor(get()) } - viewModel { ListDashboardViewModel(get(), get(), get(), get(), get(), get(), get()) } + viewModel { DashboardListViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { PrimaryCourseViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { AllEnrolledCoursesViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { LearnViewModel(get(), get()) } @@ -198,11 +198,11 @@ val screenModule = module { get() ) } - viewModel { (courseId: String, courseTitle: String, enrollmentMode: String, openBlock: String) -> + viewModel { (courseId: String, courseTitle: String, enrollmentMode: String, resumeBlockId: String) -> CourseContainerViewModel( courseId, courseTitle, - openBlock, + resumeBlockId, enrollmentMode, get(), get(), diff --git a/app/src/main/res/color/bottom_nav_color.xml b/app/src/main/res/color/bottom_nav_color.xml index 07694e22e..4e2851e90 100644 --- a/app/src/main/res/color/bottom_nav_color.xml +++ b/app/src/main/res/color/bottom_nav_color.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/app_ic_rows.xml b/app/src/main/res/drawable/app_ic_rows.xml index e068a37a6..eabe550d3 100644 --- a/app/src/main/res/drawable/app_ic_rows.xml +++ b/app/src/main/res/drawable/app_ic_rows.xml @@ -3,8 +3,8 @@ android:height="17dp" android:viewportWidth="20" android:viewportHeight="17"> - + diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 25a289a83..a9c1fd34c 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -27,4 +27,4 @@ app:layout_constraintStart_toStartOf="parent" app:menu="@menu/bottom_view_menu" /> - \ No newline at end of file + diff --git a/app/src/main/res/menu/bottom_view_menu.xml b/app/src/main/res/menu/bottom_view_menu.xml index 6285572db..f97e849f7 100644 --- a/app/src/main/res/menu/bottom_view_menu.xml +++ b/app/src/main/res/menu/bottom_view_menu.xml @@ -3,20 +3,20 @@ + android:icon="@drawable/app_ic_rows" + android:title="@string/app_navigation_learn" /> + android:icon="@drawable/app_ic_home" + android:title="@string/app_navigation_discovery" /> + android:icon="@drawable/app_ic_profile" + android:title="@string/app_navigation_profile" /> - \ No newline at end of file + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c5f93a68a..17d58ded3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -8,4 +8,4 @@ Мої курси Програми Профіль - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df82406af..baa1c2a89 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,4 @@ Learn Programs Profile - \ No newline at end of file + diff --git a/core/src/main/java/org/openedx/core/data/model/CourseAssignments.kt b/core/src/main/java/org/openedx/core/data/model/CourseAssignments.kt index ca38de44b..165868d6d 100644 --- a/core/src/main/java/org/openedx/core/data/model/CourseAssignments.kt +++ b/core/src/main/java/org/openedx/core/data/model/CourseAssignments.kt @@ -11,19 +11,19 @@ data class CourseAssignments( val pastAssignments: List? ) { fun mapToDomain() = CourseAssignments( - futureAssignments = futureAssignments?.map { + futureAssignments = futureAssignments?.mapNotNull { it.mapToDomain() }, - pastAssignments = pastAssignments?.map { + pastAssignments = pastAssignments?.mapNotNull { it.mapToDomain() } ) fun mapToRoomEntity() = CourseAssignmentsDb( - futureAssignments = futureAssignments?.map { + futureAssignments = futureAssignments?.mapNotNull { it.mapToRoomEntity() }, - pastAssignments = pastAssignments?.map { + pastAssignments = pastAssignments?.mapNotNull { it.mapToRoomEntity() } ) diff --git a/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt b/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt index 520711e94..d29e7a7ea 100644 --- a/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt +++ b/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt @@ -6,7 +6,6 @@ import kotlinx.parcelize.Parcelize import org.openedx.core.data.model.room.discovery.CourseDateBlockDb import org.openedx.core.domain.model.CourseDateBlock import org.openedx.core.utils.TimeUtils -import java.util.Date @Parcelize data class CourseDateBlock( @@ -31,27 +30,36 @@ data class CourseDateBlock( // component blockId in-case of navigating inside the app for component available in mobile @SerializedName("first_component_block_id") val blockId: String = "", -): Parcelable { - fun mapToDomain() = CourseDateBlock( - complete = complete, - date = TimeUtils.iso8601ToDate(date) ?: Date(), - assignmentType = assignmentType, - dateType = dateType, - description = description, - learnerHasAccess = learnerHasAccess, - link = link, - title = title, - blockId = blockId - ) - fun mapToRoomEntity() = CourseDateBlockDb( - complete = complete, - date = date, - assignmentType = assignmentType, - dateType = dateType, - description = description, - learnerHasAccess = learnerHasAccess, - link = link, - title = title, - blockId = blockId - ) +) : Parcelable { + fun mapToDomain(): CourseDateBlock? { + TimeUtils.iso8601ToDate(date)?.let { + return CourseDateBlock( + complete = complete, + date = it, + assignmentType = assignmentType, + dateType = dateType, + description = description, + learnerHasAccess = learnerHasAccess, + link = link, + title = title, + blockId = blockId + ) + } ?: return null + } + + fun mapToRoomEntity(): CourseDateBlockDb? { + TimeUtils.iso8601ToDate(date)?.let { + return CourseDateBlockDb( + complete = complete, + date = it, + assignmentType = assignmentType, + dateType = dateType, + description = description, + learnerHasAccess = learnerHasAccess, + link = link, + title = title, + blockId = blockId + ) + } ?: return null + } } diff --git a/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt b/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt index 4c1553dcd..a6cd1d93e 100644 --- a/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt +++ b/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt @@ -225,8 +225,8 @@ data class CourseDateBlockDb( val learnerHasAccess: Boolean = false, @ColumnInfo("complete") val complete: Boolean = false, - @ColumnInfo("date") - val date: String, + @Embedded + val date: Date, @ColumnInfo("dateType") val dateType: DateType = DateType.NONE, @ColumnInfo("assignmentType") @@ -239,7 +239,7 @@ data class CourseDateBlockDb( blockId = blockId, learnerHasAccess = learnerHasAccess, complete = complete, - date = TimeUtils.iso8601ToDate(date) ?: Date(), + date = date, dateType = dateType, assignmentType = assignmentType ) diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseStatus.kt b/core/src/main/java/org/openedx/core/domain/model/CourseStatus.kt index e25490430..134695c72 100644 --- a/core/src/main/java/org/openedx/core/domain/model/CourseStatus.kt +++ b/core/src/main/java/org/openedx/core/domain/model/CourseStatus.kt @@ -9,4 +9,4 @@ data class CourseStatus( val lastVisitedModulePath: List, val lastVisitedBlockId: String, val lastVisitedUnitDisplayName: String -): Parcelable +) : Parcelable diff --git a/core/src/main/java/org/openedx/core/domain/model/Progress.kt b/core/src/main/java/org/openedx/core/domain/model/Progress.kt index 9f021aa52..5d8ea19f8 100644 --- a/core/src/main/java/org/openedx/core/domain/model/Progress.kt +++ b/core/src/main/java/org/openedx/core/domain/model/Progress.kt @@ -9,6 +9,6 @@ data class Progress( val totalAssignmentsCount: Int, ) : Parcelable { companion object { - val DEFAULT_PROGRESS = Progress(0,0) + val DEFAULT_PROGRESS = Progress(0, 0) } } diff --git a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt index 9234ec023..29f3f48ba 100644 --- a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt +++ b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt @@ -23,6 +23,7 @@ import org.openedx.core.module.download.CurrentProgress import org.openedx.core.module.download.FileDownloader import org.openedx.core.system.notifier.DownloadNotifier import org.openedx.core.system.notifier.DownloadProgressChanged +import org.openedx.core.utils.FileUtil import java.io.File class DownloadWorker( @@ -41,11 +42,7 @@ class DownloadWorker( private var downloadEnqueue = listOf() - private val folder = File( - context.externalCacheDir.toString() + File.separator + - context.getString(R.string.app_name) - .replace(Regex("\\s"), "_") - ) + private val folder = FileUtil(context).getExternalAppDir() private var currentDownload: DownloadModel? = null private var lastUpdateTime = 0L diff --git a/core/src/main/java/org/openedx/core/module/TranscriptManager.kt b/core/src/main/java/org/openedx/core/module/TranscriptManager.kt index ba67d1a54..0b8b3e1e1 100644 --- a/core/src/main/java/org/openedx/core/module/TranscriptManager.kt +++ b/core/src/main/java/org/openedx/core/module/TranscriptManager.kt @@ -125,5 +125,4 @@ class TranscriptManager( } return null } - -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/presentation/global/InDevelopmentScreen.kt b/core/src/main/java/org/openedx/core/presentation/global/InDevelopmentScreen.kt deleted file mode 100644 index 02f633704..000000000 --- a/core/src/main/java/org/openedx/core/presentation/global/InDevelopmentScreen.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.openedx.core.presentation.global - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import org.openedx.core.ui.theme.appColors -import org.openedx.core.ui.theme.appTypography - -@Composable -fun InDevelopmentScreen( - modifier: Modifier = Modifier -) { - Box( - modifier = modifier - .fillMaxSize() - .background(MaterialTheme.appColors.secondary), - contentAlignment = Alignment.Center - ) { - Text( - modifier = Modifier.testTag("txt_in_development"), - text = "Will be available soon", - style = MaterialTheme.appTypography.headlineMedium - ) - } -} diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index 40d42971f..b637a446c 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1243,7 +1243,11 @@ fun RoundTabsBar( .then(border) .clickable { scope.launch { - pagerState.scrollToPage(index) + if (!withPager) { + rowState.animateScrollToItem(index) + } else { + pagerState.scrollToPage(index) + } onTabClicked(index) } } diff --git a/core/src/main/java/org/openedx/core/utils/FileUtil.kt b/core/src/main/java/org/openedx/core/utils/FileUtil.kt index b7d22f929..2f5c2b2e5 100644 --- a/core/src/main/java/org/openedx/core/utils/FileUtil.kt +++ b/core/src/main/java/org/openedx/core/utils/FileUtil.kt @@ -15,7 +15,7 @@ class FileUtil(val context: Context) { return file } - inline fun < reified T> saveObjectToFile(obj: T, fileName: String = "${T::class.java.simpleName}.json") { + inline fun saveObjectToFile(obj: T, fileName: String = "${T::class.java.simpleName}.json") { val gson: Gson = GsonBuilder().setPrettyPrinting().create() val jsonString = gson.toJson(obj) File(getExternalAppDir().path + fileName).writeText(jsonString) diff --git a/core/src/main/res/drawable/core_ic_settings.xml b/core/src/main/res/drawable/core_ic_settings.xml deleted file mode 100644 index a86316516..000000000 --- a/core/src/main/res/drawable/core_ic_settings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/core/src/main/res/drawable/ic_core_chapter_icon.xml b/core/src/main/res/drawable/ic_core_chapter_icon.xml index eaf899ce2..9ee00fed7 100644 --- a/core/src/main/res/drawable/ic_core_chapter_icon.xml +++ b/core/src/main/res/drawable/ic_core_chapter_icon.xml @@ -3,29 +3,28 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - - - + + + + + + diff --git a/core/src/main/res/values-night/colors.xml b/core/src/main/res/values-night/colors.xml index 4db689c7b..d6f9f1a14 100644 --- a/core/src/main/res/values-night/colors.xml +++ b/core/src/main/res/values-night/colors.xml @@ -5,4 +5,4 @@ #19212F #879FF5 #8E9BAE - \ No newline at end of file + diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index d41c58bac..57a25d9ed 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -5,4 +5,4 @@ #517BFE #3C68FF #97A5BB - \ No newline at end of file + diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt index 3dc7a06b2..2705ae1c6 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt @@ -84,7 +84,7 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) { requireArguments().getString(ARG_COURSE_ID, ""), requireArguments().getString(ARG_TITLE, ""), requireArguments().getString(ARG_ENROLLMENT_MODE, ""), - requireArguments().getString(ARG_OPEN_BLOCK, "") + requireArguments().getString(ARG_RESUME_BLOCK, "") ) } @@ -257,13 +257,13 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) { const val ARG_TITLE = "title" const val ARG_ENROLLMENT_MODE = "enrollmentMode" const val ARG_OPEN_DATES = "open_dates" - const val ARG_OPEN_BLOCK = "resume_block" + const val ARG_RESUME_BLOCK = "resume_block" fun newInstance( courseId: String, courseTitle: String, enrollmentMode: String, openDates: Boolean = false, - openBlock: String = "" + resumeBlockId: String = "" ): CourseContainerFragment { val fragment = CourseContainerFragment() fragment.arguments = bundleOf( @@ -271,7 +271,7 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) { ARG_TITLE to courseTitle, ARG_ENROLLMENT_MODE to enrollmentMode, ARG_OPEN_DATES to openDates, - ARG_OPEN_BLOCK to openBlock + ARG_RESUME_BLOCK to resumeBlockId ) return fragment } diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt index 310860933..af209660e 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt @@ -58,7 +58,7 @@ import org.openedx.core.R as CoreR class CourseContainerViewModel( val courseId: String, var courseName: String, - private var openBlock: String, + private var resumeBlockId: String, private val enrollmentMode: String, private val config: Config, private val interactor: CourseInteractor, @@ -182,9 +182,9 @@ class CourseContainerViewModel( } isReady } - if (_dataReady.value == true && openBlock.isNotEmpty()) { + if (_dataReady.value == true && resumeBlockId.isNotEmpty()) { delay(500L) - courseNotifier.send(CourseOpenBlock(openBlock)) + courseNotifier.send(CourseOpenBlock(resumeBlockId)) } } catch (e: Exception) { if (e.isInternetError() || e is NoCachedDataException) { diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt index 98478dd92..ae9754535 100644 --- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt @@ -87,7 +87,9 @@ class CourseDatesViewModel( _calendarSyncUIState.update { it.copy(isSynced = event.isSynced) } } - is RefreshDates -> loadingCourseDatesInternal() + is RefreshDates -> { + loadingCourseDatesInternal() + } } } } diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt index b767f1918..4981d12df 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt @@ -62,13 +62,13 @@ import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appTypography import org.openedx.core.ui.windowSizeValue +import org.openedx.core.utils.FileUtil import org.openedx.course.R import org.openedx.course.presentation.ui.CourseDatesBanner import org.openedx.course.presentation.ui.CourseDatesBannerTablet import org.openedx.course.presentation.ui.CourseExpandableChapterCard import org.openedx.course.presentation.ui.CourseSectionCard import org.openedx.course.presentation.ui.CourseSubSectionItem -import java.io.File import java.util.Date import org.openedx.core.R as CoreR @@ -81,12 +81,12 @@ fun CourseOutlineScreen( ) { val uiState by viewModel.uiState.collectAsState() val uiMessage by viewModel.uiMessage.collectAsState(null) - val openBlock by viewModel.openBlock.collectAsState("") + val resumeBlockId by viewModel.resumeBlockId.collectAsState("") val context = LocalContext.current - LaunchedEffect(openBlock) { - if (openBlock.isNotEmpty()) { - viewModel.openBlock(fragmentManager, openBlock) + LaunchedEffect(resumeBlockId) { + if (resumeBlockId.isNotEmpty()) { + viewModel.openBlock(fragmentManager, resumeBlockId) } } @@ -146,11 +146,7 @@ fun CourseOutlineScreen( viewModel.removeDownloadModels(it.id) } else { viewModel.saveDownloadModels( - context.externalCacheDir.toString() + - File.separator + - context - .getString(CoreR.string.app_name) - .replace(Regex("\\s"), "_"), it.id + FileUtil(context).getExternalAppDir().path, it.id ) } }, diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt index 9616ebcef..1a5c14b53 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt @@ -74,9 +74,9 @@ class CourseOutlineViewModel( val uiMessage: SharedFlow get() = _uiMessage.asSharedFlow() - private val _openBlock = MutableSharedFlow() - val openBlock: SharedFlow - get() = _openBlock.asSharedFlow() + private val _resumeBlockId = MutableSharedFlow() + val resumeBlockId: SharedFlow + get() = _resumeBlockId.asSharedFlow() private var resumeSectionBlock: Block? = null private var resumeVerticalBlock: Block? = null @@ -98,7 +98,7 @@ class CourseOutlineViewModel( } is CourseOpenBlock -> { - _openBlock.emit(event.blockId) + _resumeBlockId.emit(event.blockId) } } } diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt index 0c085fa53..b4446e0ad 100644 --- a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt +++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt @@ -76,10 +76,10 @@ import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appTypography import org.openedx.core.ui.windowSizeValue +import org.openedx.core.utils.FileUtil import org.openedx.course.R import org.openedx.course.presentation.videos.CourseVideoViewModel import org.openedx.course.presentation.videos.CourseVideosUIState -import java.io.File import java.util.Date @Composable @@ -136,11 +136,7 @@ fun CourseVideosScreen( viewModel.removeDownloadModels(it.id) } else { viewModel.saveDownloadModels( - context.externalCacheDir.toString() + - File.separator + - context - .getString(org.openedx.core.R.string.app_name) - .replace(Regex("\\s"), "_"), it.id + FileUtil(context).getExternalAppDir().path, it.id ) } }, @@ -150,11 +146,7 @@ fun CourseVideosScreen( viewModel.removeAllDownloadModels() } else { viewModel.saveAllDownloadModels( - context.externalCacheDir.toString() + - File.separator + - context - .getString(org.openedx.core.R.string.app_name) - .replace(Regex("\\s"), "_") + FileUtil(context).getExternalAppDir().path ) } }, diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt index d2af43f3c..69ca13159 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt @@ -6,12 +6,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxHeight @@ -19,9 +17,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -29,18 +25,11 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material.Card import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Search import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -56,28 +45,18 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import coil.compose.AsyncImage -import coil.request.ImageRequest import org.koin.androidx.compose.koinViewModel -import org.openedx.Lock import org.openedx.core.UIMessage import org.openedx.core.domain.model.Certificate import org.openedx.core.domain.model.CourseAssignments @@ -98,13 +77,9 @@ import org.openedx.core.ui.statusBarsInset import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes -import org.openedx.core.ui.theme.appTypography import org.openedx.core.ui.windowSizeValue -import org.openedx.core.utils.TimeUtils -import org.openedx.dashboard.R import org.openedx.dashboard.domain.CourseStatusFilter import java.util.Date -import org.openedx.core.R as CoreR class AllEnrolledCoursesFragment : Fragment() { @@ -116,7 +91,7 @@ class AllEnrolledCoursesFragment : Fragment() { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { OpenEdXTheme { - AllEnrolledCoursesScreen( + AllEnrolledCoursesView( fragmentManager = requireActivity().supportFragmentManager ) } @@ -125,7 +100,7 @@ class AllEnrolledCoursesFragment : Fragment() { } @Composable -private fun AllEnrolledCoursesScreen( +private fun AllEnrolledCoursesView( viewModel: AllEnrolledCoursesViewModel = koinViewModel(), fragmentManager: FragmentManager ) { @@ -134,7 +109,7 @@ private fun AllEnrolledCoursesScreen( val refreshing by viewModel.updating.collectAsState(false) val canLoadMore by viewModel.canLoadMore.collectAsState(false) - AllEnrolledCoursesScreen( + AllEnrolledCoursesView( apiHostUrl = viewModel.apiHostUrl, state = uiState, uiMessage = uiMessage, @@ -160,15 +135,12 @@ private fun AllEnrolledCoursesScreen( } AllEnrolledCoursesAction.Search -> { - viewModel.dashboardRouter.navigateToCourseSearch( - fragmentManager, "" - ) + viewModel.navigateToCourseSearch(fragmentManager) } is AllEnrolledCoursesAction.OpenCourse -> { with(action.enrolledCourse) { - viewModel.dashboardCourseClickedEvent(course.id, course.name) - viewModel.dashboardRouter.navigateToCourseOutline( + viewModel.navigateToCourseOutline( fragmentManager, course.id, course.name, @@ -187,7 +159,7 @@ private fun AllEnrolledCoursesScreen( @OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) @Composable -private fun AllEnrolledCoursesScreen( +private fun AllEnrolledCoursesView( apiHostUrl: String, state: AllEnrolledCoursesUIState, uiMessage: UIMessage?, @@ -431,158 +403,6 @@ private fun AllEnrolledCoursesScreen( } } -@Composable -private fun CourseItem( - modifier: Modifier = Modifier, - course: EnrolledCourse, - apiHostUrl: String, - onClick: (EnrolledCourse) -> Unit, -) { - Card( - modifier = modifier - .width(170.dp) - .height(180.dp) - .clickable { - onClick(course) - }, - backgroundColor = MaterialTheme.appColors.background, - shape = MaterialTheme.appShapes.courseImageShape, - elevation = 4.dp - ) { - Box { - Column { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(apiHostUrl + course.course.courseImage) - .error(CoreR.drawable.core_no_image_course) - .placeholder(CoreR.drawable.core_no_image_course) - .build(), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxWidth() - .height(90.dp) - ) - val progress: Float = try { - course.progress.assignmentsCompleted.toFloat() / course.progress.totalAssignmentsCount.toFloat() - } catch (_: ArithmeticException) { - 0f - } - LinearProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .height(8.dp), - progress = progress, - color = MaterialTheme.appColors.primary, - backgroundColor = MaterialTheme.appColors.divider - ) - - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .padding(top = 4.dp), - style = MaterialTheme.appTypography.labelMedium, - color = MaterialTheme.appColors.textFieldHint, - overflow = TextOverflow.Ellipsis, - minLines = 1, - maxLines = 2, - text = stringResource( - R.string.dashboard_course_date, - TimeUtils.getCourseFormattedDate( - LocalContext.current, - Date(), - course.auditAccessExpires, - course.course.start, - course.course.end, - course.course.startType, - course.course.startDisplay - ) - ) - ) - Text( - modifier = Modifier - .padding(horizontal = 8.dp, vertical = 4.dp), - text = course.course.name, - style = MaterialTheme.appTypography.titleSmall, - color = MaterialTheme.appColors.textDark, - overflow = TextOverflow.Ellipsis, - minLines = 1, - maxLines = 2 - ) - } - if (!course.course.coursewareAccess?.errorCode.isNullOrEmpty()) { - Lock() - } - } - } -} - -@Composable -private fun Header( - modifier: Modifier = Modifier, - onSearchClick: () -> Unit -) { - Box( - modifier = modifier.fillMaxWidth() - ) { - Text( - modifier = Modifier.align(Alignment.CenterStart), - text = stringResource(id = R.string.dashboard_all_courses), - color = MaterialTheme.appColors.textDark, - style = MaterialTheme.appTypography.headlineBold - ) - IconButton( - modifier = Modifier - .align(Alignment.CenterEnd) - .offset(x = 12.dp), - onClick = { - onSearchClick() - } - ) { - Icon( - imageVector = Icons.Filled.Search, - contentDescription = null, - tint = MaterialTheme.appColors.textDark - ) - } - } -} - -@Composable -private fun EmptyState( - currentCourseStatus: CourseStatusFilter -) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Column( - Modifier.width(200.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - painter = painterResource(id = R.drawable.dashboard_ic_book), - tint = MaterialTheme.appColors.textFieldBorder, - contentDescription = null - ) - Spacer(Modifier.height(4.dp)) - Text( - modifier = Modifier - .testTag("txt_empty_state_title") - .fillMaxWidth(), - text = stringResource( - id = R.string.dashboard_no_status_courses, - stringResource(currentCourseStatus.labelResId) - ), - color = MaterialTheme.appColors.textDark, - style = MaterialTheme.appTypography.titleMedium, - textAlign = TextAlign.Center - ) - } - } -} - @Preview(uiMode = UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) @Preview(uiMode = UI_MODE_NIGHT_NO, device = Devices.NEXUS_9) @@ -590,7 +410,7 @@ private fun EmptyState( @Composable private fun AllEnrolledCoursesPreview() { OpenEdXTheme { - AllEnrolledCoursesScreen( + AllEnrolledCoursesView( apiHostUrl = "http://localhost:8000", state = AllEnrolledCoursesUIState.Courses( listOf( diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt new file mode 100644 index 000000000..5c13705be --- /dev/null +++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt @@ -0,0 +1,194 @@ +package org.openedx.courses.presentation + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import org.openedx.Lock +import org.openedx.core.R +import org.openedx.core.domain.model.EnrolledCourse +import org.openedx.core.ui.theme.appColors +import org.openedx.core.ui.theme.appShapes +import org.openedx.core.ui.theme.appTypography +import org.openedx.core.utils.TimeUtils +import org.openedx.dashboard.domain.CourseStatusFilter +import java.util.Date + +@Composable +fun CourseItem( + modifier: Modifier = Modifier, + course: EnrolledCourse, + apiHostUrl: String, + onClick: (EnrolledCourse) -> Unit, +) { + Card( + modifier = modifier + .width(170.dp) + .height(180.dp) + .clickable { + onClick(course) + }, + backgroundColor = MaterialTheme.appColors.background, + shape = MaterialTheme.appShapes.courseImageShape, + elevation = 4.dp + ) { + Box { + Column { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(apiHostUrl + course.course.courseImage) + .error(R.drawable.core_no_image_course) + .placeholder(R.drawable.core_no_image_course) + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(90.dp) + ) + val progress: Float = try { + course.progress.assignmentsCompleted.toFloat() / course.progress.totalAssignmentsCount.toFloat() + } catch (_: ArithmeticException) { + 0f + } + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(8.dp), + progress = progress, + color = MaterialTheme.appColors.primary, + backgroundColor = MaterialTheme.appColors.divider + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(top = 4.dp), + style = MaterialTheme.appTypography.labelMedium, + color = MaterialTheme.appColors.textFieldHint, + overflow = TextOverflow.Ellipsis, + minLines = 1, + maxLines = 2, + text = stringResource( + org.openedx.dashboard.R.string.dashboard_course_date, + TimeUtils.getCourseFormattedDate( + LocalContext.current, + Date(), + course.auditAccessExpires, + course.course.start, + course.course.end, + course.course.startType, + course.course.startDisplay + ) + ) + ) + Text( + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp), + text = course.course.name, + style = MaterialTheme.appTypography.titleSmall, + color = MaterialTheme.appColors.textDark, + overflow = TextOverflow.Ellipsis, + minLines = 1, + maxLines = 2 + ) + } + if (!course.course.coursewareAccess?.errorCode.isNullOrEmpty()) { + Lock() + } + } + } +} + +@Composable +fun Header( + modifier: Modifier = Modifier, + onSearchClick: () -> Unit +) { + Box( + modifier = modifier.fillMaxWidth() + ) { + Text( + modifier = Modifier.align(Alignment.CenterStart), + text = stringResource(id = org.openedx.dashboard.R.string.dashboard_all_courses), + color = MaterialTheme.appColors.textDark, + style = MaterialTheme.appTypography.headlineBold + ) + IconButton( + modifier = Modifier + .align(Alignment.CenterEnd) + .offset(x = 12.dp), + onClick = { + onSearchClick() + } + ) { + Icon( + imageVector = Icons.Filled.Search, + contentDescription = null, + tint = MaterialTheme.appColors.textDark + ) + } + } +} + +@Composable +fun EmptyState( + currentCourseStatus: CourseStatusFilter +) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + Modifier.width(200.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = org.openedx.dashboard.R.drawable.dashboard_ic_book), + tint = MaterialTheme.appColors.textFieldBorder, + contentDescription = null + ) + Spacer(Modifier.height(4.dp)) + Text( + modifier = Modifier + .testTag("txt_empty_state_title") + .fillMaxWidth(), + text = stringResource( + id = org.openedx.dashboard.R.string.dashboard_no_status_courses, + stringResource(currentCourseStatus.labelResId) + ), + color = MaterialTheme.appColors.textDark, + style = MaterialTheme.appTypography.titleMedium, + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt index 536a5f335..86f2b8c89 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt @@ -1,5 +1,6 @@ package org.openedx.courses.presentation +import androidx.fragment.app.FragmentManager import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow @@ -31,7 +32,7 @@ class AllEnrolledCoursesViewModel( private val resourceManager: ResourceManager, private val discoveryNotifier: DiscoveryNotifier, private val analytics: DashboardAnalytics, - val dashboardRouter: DashboardRouter + private val dashboardRouter: DashboardRouter ) : BaseViewModel() { val apiHostUrl get() = config.getApiHostURL() @@ -159,7 +160,7 @@ class AllEnrolledCoursesViewModel( } } - fun dashboardCourseClickedEvent(courseId: String, courseName: String) { + private fun dashboardCourseClickedEvent(courseId: String, courseName: String) { analytics.dashboardCourseClickedEvent(courseId, courseName) } @@ -172,6 +173,27 @@ class AllEnrolledCoursesViewModel( } } } + + fun navigateToCourseSearch(fragmentManager: FragmentManager) { + dashboardRouter.navigateToCourseSearch( + fragmentManager, "" + ) + } + + fun navigateToCourseOutline( + fragmentManager: FragmentManager, + courseId: String, + courseName: String, + mode: String + ) { + dashboardCourseClickedEvent(courseId, courseName) + dashboardRouter.navigateToCourseOutline( + fragmentManager, + courseId, + courseName, + mode + ) + } } interface AllEnrolledCoursesAction { diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseFragment.kt b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseFragment.kt index e911248f9..e1ea7f596 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseFragment.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseFragment.kt @@ -17,7 +17,7 @@ class PrimaryCourseFragment : Fragment() { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { OpenEdXTheme { - PrimaryCourseScreen(fragmentManager = requireActivity().supportFragmentManager) + PrimaryCourseView(fragmentManager = requireActivity().supportFragmentManager) } } } diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseScreen.kt b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseView.kt similarity index 94% rename from dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseScreen.kt rename to dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseView.kt index ac98d29db..a07fc6457 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseScreen.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseView.kt @@ -96,7 +96,7 @@ import java.util.Date import org.openedx.core.R as CoreR @Composable -fun PrimaryCourseScreen( +fun PrimaryCourseView( viewModel: PrimaryCourseViewModel = koinViewModel(), fragmentManager: FragmentManager, ) { @@ -104,7 +104,7 @@ fun PrimaryCourseScreen( val uiMessage by viewModel.uiMessage.collectAsState(null) val uiState by viewModel.uiState.collectAsState(PrimaryCourseUIState.Loading) - PrimaryCourseScreen( + PrimaryCourseView( uiMessage = uiMessage, uiState = uiState, updating = updating, @@ -117,7 +117,7 @@ fun PrimaryCourseScreen( } PrimaryCourseScreenAction.ViewAll -> { - viewModel.dashboardRouter.navigateToAllEnrolledCourses(fragmentManager) + viewModel.navigateToAllEnrolledCourses(fragmentManager) } PrimaryCourseScreenAction.Reload -> { @@ -129,31 +129,25 @@ fun PrimaryCourseScreen( } is PrimaryCourseScreenAction.OpenCourse -> { - viewModel.dashboardRouter.navigateToCourseOutline( - fm = fragmentManager, - courseId = action.enrolledCourse.course.id, - courseTitle = action.enrolledCourse.course.name, - enrollmentMode = action.enrolledCourse.mode + viewModel.navigateToCourseOutline( + fragmentManager = fragmentManager, + enrolledCourse = action.enrolledCourse ) } is PrimaryCourseScreenAction.NavigateToDates -> { - viewModel.dashboardRouter.navigateToCourseOutline( - fm = fragmentManager, - courseId = action.enrolledCourse.course.id, - courseTitle = action.enrolledCourse.course.name, - enrollmentMode = action.enrolledCourse.mode, + viewModel.navigateToCourseOutline( + fragmentManager = fragmentManager, + enrolledCourse = action.enrolledCourse, openDates = true ) } is PrimaryCourseScreenAction.OpenBlock -> { - viewModel.dashboardRouter.navigateToCourseOutline( - fm = fragmentManager, - courseId = action.enrolledCourse.course.id, - courseTitle = action.enrolledCourse.course.name, - enrollmentMode = action.enrolledCourse.mode, - openBlock = action.blockId + viewModel.navigateToCourseOutline( + fragmentManager = fragmentManager, + enrolledCourse = action.enrolledCourse, + resumeBlockId = action.blockId ) } } @@ -163,7 +157,7 @@ fun PrimaryCourseScreen( @OptIn(ExperimentalMaterialApi::class) @Composable -private fun PrimaryCourseScreen( +private fun PrimaryCourseView( uiMessage: UIMessage?, uiState: PrimaryCourseUIState, updating: Boolean, @@ -225,7 +219,7 @@ private fun PrimaryCourseScreen( navigateToDates = { onAction(PrimaryCourseScreenAction.NavigateToDates(it)) }, - openBlock = { course, blockId -> + resumeBlockId = { course, blockId -> onAction(PrimaryCourseScreenAction.OpenBlock(course, blockId)) } ) @@ -279,7 +273,7 @@ private fun UserCourses( openCourse: (EnrolledCourse) -> Unit, navigateToDates: (EnrolledCourse) -> Unit, onViewAllClick: () -> Unit, - openBlock: (enrolledCourse: EnrolledCourse, blockId: String) -> Unit, + resumeBlockId: (enrolledCourse: EnrolledCourse, blockId: String) -> Unit, ) { Column( modifier = modifier @@ -291,7 +285,7 @@ private fun UserCourses( primaryCourse = primaryCourse, apiHostUrl = apiHostUrl, navigateToDates = navigateToDates, - openBlock = openBlock, + resumeBlockId = resumeBlockId, openCourse = openCourse ) } @@ -504,7 +498,7 @@ private fun PrimaryCourseCard( primaryCourse: EnrolledCourse, apiHostUrl: String, navigateToDates: (EnrolledCourse) -> Unit, - openBlock: (enrolledCourse: EnrolledCourse, blockId: String) -> Unit, + resumeBlockId: (enrolledCourse: EnrolledCourse, blockId: String) -> Unit, openCourse: (EnrolledCourse) -> Unit, ) { val context = LocalContext.current @@ -563,7 +557,7 @@ private fun PrimaryCourseCard( AssignmentItem( modifier = Modifier.clickable { if (pastAssignments.size == 1) { - openBlock(primaryCourse, nearestAssignment.blockId) + resumeBlockId(primaryCourse, nearestAssignment.blockId) } else { navigateToDates(primaryCourse) } @@ -581,7 +575,7 @@ private fun PrimaryCourseCard( AssignmentItem( modifier = Modifier.clickable { if (futureAssignments.size == 1) { - openBlock(primaryCourse, nearestAssignment.blockId) + resumeBlockId(primaryCourse, nearestAssignment.blockId) } else { navigateToDates(primaryCourse) } @@ -601,7 +595,7 @@ private fun PrimaryCourseCard( if (primaryCourse.courseStatus == null) { openCourse(primaryCourse) } else { - openBlock(primaryCourse, primaryCourse.courseStatus?.lastVisitedBlockId ?: "") + resumeBlockId(primaryCourse, primaryCourse.courseStatus?.lastVisitedBlockId ?: "") } } ) @@ -849,7 +843,7 @@ private fun ViewAllItemPreview() { @Composable private fun PrimaryCourseScreenPreview() { OpenEdXTheme { - PrimaryCourseScreen( + PrimaryCourseView( uiState = PrimaryCourseUIState.Courses(mockUserCourses), apiHostUrl = "", uiMessage = null, diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseViewModel.kt index 326adc837..1f468dfc9 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseViewModel.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/PrimaryCourseViewModel.kt @@ -1,5 +1,6 @@ package org.openedx.courses.presentation +import androidx.fragment.app.FragmentManager import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -31,7 +32,7 @@ class PrimaryCourseViewModel( private val discoveryNotifier: DiscoveryNotifier, private val networkConnection: NetworkConnection, private val fileUtil: FileUtil, - val dashboardRouter: DashboardRouter + private val dashboardRouter: DashboardRouter ) : BaseViewModel() { val apiHostUrl get() = config.getApiHostURL() @@ -95,6 +96,26 @@ class PrimaryCourseViewModel( viewModelScope.launch { discoveryNotifier.send(NavigationToDiscovery()) } } + fun navigateToAllEnrolledCourses(fragmentManager: FragmentManager) { + dashboardRouter.navigateToAllEnrolledCourses(fragmentManager) + } + + fun navigateToCourseOutline( + fragmentManager: FragmentManager, + enrolledCourse: EnrolledCourse, + openDates: Boolean = false, + resumeBlockId: String = "" + ) { + dashboardRouter.navigateToCourseOutline( + fm = fragmentManager, + courseId = enrolledCourse.course.id, + courseTitle = enrolledCourse.course.name, + enrollmentMode = enrolledCourse.mode, + openDates = openDates, + resumeBlockId = resumeBlockId + ) + } + private fun collectDiscoveryNotifier() { viewModelScope.launch { discoveryNotifier.notifier.collect { diff --git a/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt b/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt index 5844e8415..22637f48c 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt @@ -36,9 +36,8 @@ class DashboardRepository( } suspend fun getMainUserCourses(): CourseEnrollments { - val user = preferencesManager.user val result = api.getUserCourses( - username = user?.username ?: "", + username = preferencesManager.user?.username ?: "", ) preferencesManager.appConfig = result.configs.mapToDomain() diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardFragment.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt similarity index 99% rename from dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardFragment.kt rename to dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt index 2e5ca4033..d2dfef91a 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardFragment.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt @@ -103,7 +103,7 @@ import org.openedx.core.R as CoreR class ListDashboardFragment : Fragment() { - private val viewModel by viewModel() + private val viewModel by viewModel() private val router by inject() override fun onCreate(savedInstanceState: Bundle?) { diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardViewModel.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt similarity index 99% rename from dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardViewModel.kt rename to dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt index f2cd06090..812e52f2e 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/ListDashboardViewModel.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt @@ -20,7 +20,7 @@ import org.openedx.core.system.notifier.CourseDashboardUpdate import org.openedx.core.system.notifier.DiscoveryNotifier import org.openedx.dashboard.domain.interactor.DashboardInteractor -class ListDashboardViewModel( +class DashboardListViewModel( private val config: Config, private val networkConnection: NetworkConnection, private val interactor: DashboardInteractor, diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardRouter.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardRouter.kt index b025c3748..fb90b44e8 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardRouter.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardRouter.kt @@ -11,7 +11,7 @@ interface DashboardRouter { courseTitle: String, enrollmentMode: String, openDates: Boolean = false, - openBlock: String = "" + resumeBlockId: String = "" ) fun navigateToSettings(fm: FragmentManager) diff --git a/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt b/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt index a4127d37e..ffc1e964a 100644 --- a/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt +++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt @@ -3,7 +3,6 @@ package org.openedx.learn.presentation import android.os.Bundle import android.view.View import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -167,7 +166,6 @@ private fun Title( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun LearnDropdownMenu( modifier: Modifier = Modifier, @@ -264,7 +262,6 @@ private fun HeaderPreview() { } } -@OptIn(ExperimentalFoundationApi::class) @Preview @Composable private fun LearnDropdownMenuPreview() { diff --git a/dashboard/src/main/res/drawable/dashboard_ic_book.xml b/dashboard/src/main/res/drawable/dashboard_ic_book.xml index a26c83ec7..dd802ee92 100644 --- a/dashboard/src/main/res/drawable/dashboard_ic_book.xml +++ b/dashboard/src/main/res/drawable/dashboard_ic_book.xml @@ -6,39 +6,39 @@ + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + android:strokeLineCap="round" + android:strokeLineJoin="round" /> diff --git a/dashboard/src/main/res/values/strings.xml b/dashboard/src/main/res/values/strings.xml index 33f794904..4ca0c4fce 100644 --- a/dashboard/src/main/res/values/strings.xml +++ b/dashboard/src/main/res/values/strings.xml @@ -7,7 +7,7 @@ Learn Programs Course %1$s - Start course + Start Course Resume Course %1$d Past Due Assignments View All Courses (%1$d) @@ -18,7 +18,6 @@ Completed Expired All Courses - All No Courses You are not currently enrolled in any courses, would you like to explore the course catalog? Find a Course diff --git a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt index 4fd957e12..6ca20a255 100644 --- a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt +++ b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardViewModelTest.kt @@ -77,7 +77,7 @@ class DashboardViewModelTest { @Test fun `getCourses no internet connection`() = runTest { - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -101,7 +101,7 @@ class DashboardViewModelTest { @Test fun `getCourses unknown error`() = runTest { - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -125,7 +125,7 @@ class DashboardViewModelTest { @Test fun `getCourses from network`() = runTest { - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -149,7 +149,7 @@ class DashboardViewModelTest { @Test fun `getCourses from network with next page`() = runTest { - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -183,7 +183,7 @@ class DashboardViewModelTest { fun `getCourses from cache`() = runTest { every { networkConnection.isOnline() } returns false coEvery { interactor.getEnrolledCoursesFromCache() } returns listOf(mockk()) - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -207,7 +207,7 @@ class DashboardViewModelTest { fun `updateCourses no internet error`() = runTest { every { networkConnection.isOnline() } returns true coEvery { interactor.getEnrolledCourses(any()) } returns dashboardCourseList - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -235,7 +235,7 @@ class DashboardViewModelTest { fun `updateCourses unknown exception`() = runTest { every { networkConnection.isOnline() } returns true coEvery { interactor.getEnrolledCourses(any()) } returns dashboardCourseList - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -263,7 +263,7 @@ class DashboardViewModelTest { fun `updateCourses success`() = runTest { every { networkConnection.isOnline() } returns true coEvery { interactor.getEnrolledCourses(any()) } returns dashboardCourseList - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -296,7 +296,7 @@ class DashboardViewModelTest { "" ) ) - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor, @@ -321,7 +321,7 @@ class DashboardViewModelTest { @Test fun `CourseDashboardUpdate notifier test`() = runTest { coEvery { discoveryNotifier.notifier } returns flow { emit(CourseDashboardUpdate()) } - val viewModel = ListDashboardViewModel( + val viewModel = DashboardListViewModel( config, networkConnection, interactor,