From b9d88746aca5ee94e391cc4f35876ba35e66568e Mon Sep 17 00:00:00 2001 From: vladkash Date: Tue, 25 Apr 2023 15:43:12 +0500 Subject: [PATCH 01/13] todos --- .../hyperskill/app/study_plan/domain/model/StudyPlanSection.kt | 3 ++- .../study_plan/widget/presentation/StudyPlanWidgetReducer.kt | 3 ++- .../study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt index 3a6eb11548..2de0560e94 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt @@ -26,5 +26,6 @@ data class StudyPlanSection( @SerialName("seconds_to_complete") val secondsToComplete: Float? = null, @SerialName("activities") - val activities: List + val activities: List, + // TODO: add section type field, using enum to detect root topics section ) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index c54b4a44f7..30d2c80272 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -196,7 +196,8 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOf( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = section.studyPlanSection.activities + activitiesIds = section.studyPlanSection.activities, + // TODO: add parameter for pagination activities ) ) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt index 6c769e01d5..d108464220 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt @@ -91,6 +91,7 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { title = activity.title.ifBlank { activity.id.toString() }, state = when (activity.state) { LearningActivityState.TODO -> if (activity.isCurrent) { + // TODO change detection next activity depending StudyPlanWidgetViewState.SectionItemState.NEXT } else { StudyPlanWidgetViewState.SectionItemState.LOCKED From f4d50ad35ceef614e75b2965a699cd91eb3e0cf9 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 12:11:22 +0500 Subject: [PATCH 02/13] root topics pagination & new detecting next activity algorithm --- .../domain/model/LearningActivity.kt | 6 ++++- .../domain/model/StudyPlanSection.kt | 8 ++++-- .../domain/model/StudyPlanSectionType.kt | 10 ++++++++ .../StudyPlanWidgetActionDispatcher.kt | 3 +++ .../presentation/StudyPlanWidgetFeature.kt | 11 ++++++-- .../presentation/StudyPlanWidgetReducer.kt | 25 +++++++++++++++---- .../view/StudyPlanWidgetViewStateMapper.kt | 12 ++++----- 7 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSectionType.kt diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt index 5ca18abc25..bd56868ec9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt @@ -2,6 +2,7 @@ package org.hyperskill.app.learning_activities.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient @Serializable data class LearningActivity( @@ -20,7 +21,10 @@ data class LearningActivity( @SerialName("title") val title: String = "", @SerialName("hypercoins_award") - val hypercoinsAward: Int = 0 + val hypercoinsAward: Int = 0, + + @Transient + var sectionId: Long? = null ) { val state: LearningActivityState? get() = LearningActivityState.getByValue(stateValue) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt index 2de0560e94..bd11e4e40b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt @@ -27,5 +27,9 @@ data class StudyPlanSection( val secondsToComplete: Float? = null, @SerialName("activities") val activities: List, - // TODO: add section type field, using enum to detect root topics section -) \ No newline at end of file + @SerialName("type") + val type: StudyPlanSectionType? = null +) + +fun StudyPlanSection.isRootTopicsSection(): Boolean = + type == StudyPlanSectionType.ROOT_TOPICS \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSectionType.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSectionType.kt new file mode 100644 index 0000000000..b3a6221048 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSectionType.kt @@ -0,0 +1,10 @@ +package org.hyperskill.app.study_plan.domain.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class StudyPlanSectionType { + @SerialName("root topics") + ROOT_TOPICS +} diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt index c45891f914..c0b1b08df5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt @@ -46,6 +46,9 @@ class StudyPlanWidgetActionDispatcher( is InternalAction.FetchActivities -> { studyPlanInteractor.getLearningActivities(action.activitiesIds, action.types, action.states) .onSuccess { learningActivities -> + learningActivities.forEach { learningActivity -> + learningActivity.sectionId = action.sectionId + } onNewMessage( StudyPlanWidgetFeature.LearningActivitiesFetchResult.Success( action.sectionId, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index 1ccee94a0a..06c6caa464 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -1,19 +1,26 @@ package org.hyperskill.app.study_plan.widget.presentation -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection +import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection import org.hyperskill.app.track.domain.model.Track +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds + fun isNextActivity(activity: LearningActivity, section: StudyPlanSection): Boolean { + val firstActivityId = section.activities.firstOrNull() ?: return false + + return (section.isRootTopicsSection() && activity.isCurrent) || activity.id == firstActivityId + } + data class State( val studyPlan: StudyPlan? = null, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index 30d2c80272..095377ee18 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -4,6 +4,7 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivityTarge import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.study_plan.domain.model.StudyPlanStatus +import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.Action import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.InternalAction import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.Message @@ -14,6 +15,10 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StudyPlanWidgetReducerResult = Pair> class StudyPlanWidgetReducer : StateReducer { + companion object { + const val ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT = 10 + } + override fun reduce(state: State, message: Message): StudyPlanWidgetReducerResult = when (message) { Message.Initialize -> coldContentFetch() @@ -168,7 +173,11 @@ class StudyPlanWidgetReducer : StateReducer { ) to setOf( InternalAction.FetchActivities( sectionId = message.sectionId, - activitiesIds = section.studyPlanSection.activities + activitiesIds = if (section.studyPlanSection.isRootTopicsSection()) { + section.studyPlanSection.activities.take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) + } else { + section.studyPlanSection.activities + } ) ) } @@ -196,8 +205,11 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOf( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = section.studyPlanSection.activities, - // TODO: add parameter for pagination activities + activitiesIds = if (section.studyPlanSection.isRootTopicsSection()) { + section.studyPlanSection.activities.take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) + } else { + section.studyPlanSection.activities + } ) ) } @@ -214,8 +226,11 @@ class StudyPlanWidgetReducer : StateReducer { } private fun handleActivityClicked(state: State, activityId: Long): StudyPlanWidgetReducerResult { - val activity = state.activities[activityId] - if (activity?.isCurrent != true) return state to emptySet() + val activity = state.activities[activityId] ?: return state to emptySet() + val section = state.studyPlanSections[activity.sectionId]?.studyPlanSection ?: return state to emptySet() + + if (!StudyPlanWidgetFeature.isNextActivity(activity, section)) return state to emptySet() + val action = when (activity.type) { LearningActivityType.IMPLEMENT_STAGE -> { val projectId = state.studyPlan?.projectId diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt index b46bc747d3..b0d51d9e2d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt @@ -1,13 +1,14 @@ package org.hyperskill.app.study_plan.widget.view -import kotlin.math.roundToLong import org.hyperskill.app.core.view.mapper.DateFormatter import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState +import org.hyperskill.app.study_plan.domain.model.StudyPlanSection import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.firstVisibleSection import org.hyperskill.app.study_plan.widget.presentation.getSectionActivities import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewState.SectionContent +import kotlin.math.roundToLong class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { fun map(state: StudyPlanWidgetFeature.State): StudyPlanWidgetViewState = @@ -69,14 +70,14 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { if (activities.isEmpty()) { SectionContent.Loading } else { - getContent(activities) + getContent(activities, sectionInfo.studyPlanSection) } } StudyPlanWidgetFeature.ContentStatus.ERROR -> SectionContent.Error StudyPlanWidgetFeature.ContentStatus.LOADED -> { val activities = state.getSectionActivities(sectionInfo.studyPlanSection.id) if (activities.isNotEmpty()) { - getContent(activities) + getContent(activities, sectionInfo.studyPlanSection) } else { SectionContent.Error } @@ -86,15 +87,14 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { SectionContent.Collapsed } - private fun getContent(activities: List): SectionContent.Content = + private fun getContent(activities: List, section: StudyPlanSection): SectionContent.Content = SectionContent.Content( sectionItems = activities.map { activity -> StudyPlanWidgetViewState.SectionItem( id = activity.id, title = activity.title.ifBlank { activity.id.toString() }, state = when (activity.state) { - LearningActivityState.TODO -> if (activity.isCurrent) { - // TODO change detection next activity depending + LearningActivityState.TODO -> if (StudyPlanWidgetFeature.isNextActivity(activity, section)) { StudyPlanWidgetViewState.SectionItemState.NEXT } else { StudyPlanWidgetViewState.SectionItemState.LOCKED From b8055b56875608223e047c267923550810b4a111 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 12:27:33 +0500 Subject: [PATCH 03/13] fix next activity detection --- .../widget/presentation/StudyPlanWidgetFeature.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index a550043676..9cba3ff224 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -1,7 +1,5 @@ package org.hyperskill.app.study_plan.widget.presentation -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState @@ -11,15 +9,18 @@ import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection import org.hyperskill.app.track.domain.model.Track +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds - fun isNextActivity(activity: LearningActivity, section: StudyPlanSection): Boolean { - val firstActivityId = section.activities.firstOrNull() ?: return false - - return (section.isRootTopicsSection() && activity.isCurrent) || activity.id == firstActivityId - } + fun isNextActivity(activity: LearningActivity, section: StudyPlanSection): Boolean = + if (section.isRootTopicsSection() && activity.isCurrent) { + true + } else { + section.activities.firstOrNull() == activity.id + } data class State( val studyPlan: StudyPlan? = null, From 443d14b3447bc34cb6809f23891660a2b1a4e651 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 14:19:29 +0500 Subject: [PATCH 04/13] fix next activity detection --- .../presentation/StudyPlanWidgetFeature.kt | 15 +++++++------ .../presentation/StudyPlanWidgetReducer.kt | 2 +- .../view/StudyPlanWidgetViewStateMapper.kt | 21 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index 9cba3ff224..908d008a6b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -15,12 +15,15 @@ import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds - fun isNextActivity(activity: LearningActivity, section: StudyPlanSection): Boolean = - if (section.isRootTopicsSection() && activity.isCurrent) { - true - } else { - section.activities.firstOrNull() == activity.id - } + fun getNextActivityId(section: StudyPlanSection, state: State): Long? = + state.getSectionActivities(section.id) + .firstOrNull { + if (section.isRootTopicsSection()) { + it.isCurrent + } else { + it.state == LearningActivityState.TODO + } + }?.id data class State( val studyPlan: StudyPlan? = null, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index b0a3c093f4..e2293bae6e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -254,7 +254,7 @@ class StudyPlanWidgetReducer : StateReducer { val activity = state.activities[activityId] ?: return state to emptySet() val section = state.studyPlanSections[activity.sectionId]?.studyPlanSection ?: return state to emptySet() - if (!StudyPlanWidgetFeature.isNextActivity(activity, section)) return state to emptySet() + if (StudyPlanWidgetFeature.getNextActivityId(section, state) != activityId) return state to emptySet() val action = when (activity.type) { LearningActivityType.IMPLEMENT_STAGE -> { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt index 70c8e97ebf..0e70bbe9a0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt @@ -3,7 +3,6 @@ package org.hyperskill.app.study_plan.widget.view import org.hyperskill.app.core.view.mapper.DateFormatter import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState -import org.hyperskill.app.study_plan.domain.model.StudyPlanSection import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.firstVisibleSection import org.hyperskill.app.study_plan.widget.presentation.getSectionActivities @@ -70,14 +69,26 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { if (activities.isEmpty()) { SectionContent.Loading } else { - getContent(activities, sectionInfo.studyPlanSection) + getContent( + activities = activities, + nextActivityId = StudyPlanWidgetFeature.getNextActivityId( + section = sectionInfo.studyPlanSection, + state = state + ) + ) } } StudyPlanWidgetFeature.ContentStatus.ERROR -> SectionContent.Error StudyPlanWidgetFeature.ContentStatus.LOADED -> { val activities = state.getSectionActivities(sectionInfo.studyPlanSection.id) if (activities.isNotEmpty()) { - getContent(activities, sectionInfo.studyPlanSection) + getContent( + activities = activities, + nextActivityId = StudyPlanWidgetFeature.getNextActivityId( + section = sectionInfo.studyPlanSection, + state = state + ) + ) } else { SectionContent.Error } @@ -87,14 +98,14 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { SectionContent.Collapsed } - private fun getContent(activities: List, section: StudyPlanSection): SectionContent.Content = + private fun getContent(activities: List, nextActivityId: Long?): SectionContent.Content = SectionContent.Content( sectionItems = activities.map { activity -> StudyPlanWidgetViewState.SectionItem( id = activity.id, title = activity.title.ifBlank { activity.id.toString() }, state = when (activity.state) { - LearningActivityState.TODO -> if (StudyPlanWidgetFeature.isNextActivity(activity, section)) { + LearningActivityState.TODO -> if (activity.id == nextActivityId) { StudyPlanWidgetViewState.SectionItemState.NEXT } else { StudyPlanWidgetViewState.SectionItemState.LOCKED From 14803fba8d7c873286edfa6128f07d801a7572c8 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 14:40:33 +0500 Subject: [PATCH 05/13] fix next activity detection --- .../widget/presentation/StudyPlanWidgetFeature.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index 908d008a6b..01b0892c4e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -15,8 +15,10 @@ import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds - fun getNextActivityId(section: StudyPlanSection, state: State): Long? = - state.getSectionActivities(section.id) + fun getNextActivityId(section: StudyPlanSection, state: State): Long? { + if(state.firstVisibleSection()?.id != section.id) return null + + return state.getSectionActivities(section.id) .firstOrNull { if (section.isRootTopicsSection()) { it.isCurrent @@ -24,6 +26,8 @@ object StudyPlanWidgetFeature { it.state == LearningActivityState.TODO } }?.id + } + data class State( val studyPlan: StudyPlan? = null, From 15e4718a4b6cbf961d55df381069ecffda4bc5f8 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 18:06:12 +0500 Subject: [PATCH 06/13] fix pagination & refactoring --- .../study_plan/domain/model/StudyPlanSection.kt | 2 ++ .../widget/presentation/StateExtentions.kt | 15 +++++++++++++++ .../widget/presentation/StudyPlanWidgetFeature.kt | 15 --------------- .../widget/presentation/StudyPlanWidgetReducer.kt | 14 +++++++++----- .../widget/view/StudyPlanWidgetViewStateMapper.kt | 11 +++-------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt index bd11e4e40b..9a977d9999 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt @@ -13,6 +13,8 @@ data class StudyPlanSection( val targetId: Long? = null, @SerialName("target_type") val targetType: String? = null, + @SerialName("next_activity_id") + val nextActivityId: Long? = null, @SerialName("is_visible") val isVisible: Boolean, @SerialName("title") diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt index 7b47e04f24..91b31ff710 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt @@ -1,7 +1,9 @@ package org.hyperskill.app.study_plan.widget.presentation import org.hyperskill.app.learning_activities.domain.model.LearningActivity +import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.study_plan.domain.model.StudyPlanSection +import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection internal fun StudyPlanWidgetFeature.State.firstVisibleSection(): StudyPlanSection? = studyPlanSections.values @@ -13,3 +15,16 @@ internal fun StudyPlanWidgetFeature.State.getSectionActivities(sectionId: Long): ?.studyPlanSection ?.activities ?.mapNotNull { id -> activities[id] } ?: emptyList() + +internal fun StudyPlanWidgetFeature.State.getNextActivityId(section: StudyPlanSection): Long? { + if (firstVisibleSection()?.id != section.id) return null + + return getSectionActivities(section.id) + .firstOrNull { + if (section.isRootTopicsSection()) { + it.isCurrent + } else { + it.state == LearningActivityState.TODO + } + }?.id +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index 01b0892c4e..bea3216c04 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -7,7 +7,6 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection -import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection import org.hyperskill.app.track.domain.model.Track import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -15,20 +14,6 @@ import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds - fun getNextActivityId(section: StudyPlanSection, state: State): Long? { - if(state.firstVisibleSection()?.id != section.id) return null - - return state.getSectionActivities(section.id) - .firstOrNull { - if (section.isRootTopicsSection()) { - it.isCurrent - } else { - it.state == LearningActivityState.TODO - } - }?.id - } - - data class State( val studyPlan: StudyPlan? = null, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index e2293bae6e..b667bd4512 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -230,10 +230,14 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOf( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = if (section.studyPlanSection.isRootTopicsSection()) { - section.studyPlanSection.activities.take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) - } else { - section.studyPlanSection.activities + activitiesIds = section.studyPlanSection.activities.apply { + if (section.studyPlanSection.isRootTopicsSection() && + section.studyPlanSection.nextActivityId != null + ) { + dropWhile { it != section.studyPlanSection.nextActivityId } + } + + take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) } ) ) @@ -254,7 +258,7 @@ class StudyPlanWidgetReducer : StateReducer { val activity = state.activities[activityId] ?: return state to emptySet() val section = state.studyPlanSections[activity.sectionId]?.studyPlanSection ?: return state to emptySet() - if (StudyPlanWidgetFeature.getNextActivityId(section, state) != activityId) return state to emptySet() + if (state.getNextActivityId(section) != activityId) return state to emptySet() val action = when (activity.type) { LearningActivityType.IMPLEMENT_STAGE -> { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt index 0e70bbe9a0..23967749c6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt @@ -5,6 +5,7 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.firstVisibleSection +import org.hyperskill.app.study_plan.widget.presentation.getNextActivityId import org.hyperskill.app.study_plan.widget.presentation.getSectionActivities import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewState.SectionContent import kotlin.math.roundToLong @@ -71,10 +72,7 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { } else { getContent( activities = activities, - nextActivityId = StudyPlanWidgetFeature.getNextActivityId( - section = sectionInfo.studyPlanSection, - state = state - ) + nextActivityId = state.getNextActivityId(sectionInfo.studyPlanSection) ) } } @@ -84,10 +82,7 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { if (activities.isNotEmpty()) { getContent( activities = activities, - nextActivityId = StudyPlanWidgetFeature.getNextActivityId( - section = sectionInfo.studyPlanSection, - state = state - ) + nextActivityId = state.getNextActivityId(section = sectionInfo.studyPlanSection) ) } else { SectionContent.Error From f4299c07cde900b3d4ffb6b1bab89e617a2d3d42 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 18:57:13 +0500 Subject: [PATCH 07/13] fix pagination --- .../presentation/StudyPlanWidgetReducer.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index b667bd4512..e7e4d45ef8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -198,11 +198,7 @@ class StudyPlanWidgetReducer : StateReducer { ) to setOf( InternalAction.FetchActivities( sectionId = message.sectionId, - activitiesIds = if (section.studyPlanSection.isRootTopicsSection()) { - section.studyPlanSection.activities.take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) - } else { - section.studyPlanSection.activities - } + activitiesIds = paginateActivitiesToFetch(section) ) ) } @@ -230,15 +226,7 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOf( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = section.studyPlanSection.activities.apply { - if (section.studyPlanSection.isRootTopicsSection() && - section.studyPlanSection.nextActivityId != null - ) { - dropWhile { it != section.studyPlanSection.nextActivityId } - } - - take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) - } + activitiesIds = paginateActivitiesToFetch(section) ) ) } @@ -254,6 +242,17 @@ class StudyPlanWidgetReducer : StateReducer { } } + private fun paginateActivitiesToFetch(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = + section.studyPlanSection.activities.apply { + if (section.studyPlanSection.isRootTopicsSection() && + section.studyPlanSection.nextActivityId != null + ) { + dropWhile { it != section.studyPlanSection.nextActivityId } + + take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) + } + } + private fun handleActivityClicked(state: State, activityId: Long): StudyPlanWidgetReducerResult { val activity = state.activities[activityId] ?: return state to emptySet() val section = state.studyPlanSections[activity.sectionId]?.studyPlanSection ?: return state to emptySet() From f2bd53ef6e33669533cc56da14aa8c123be78296 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 18:59:41 +0500 Subject: [PATCH 08/13] fix pagination --- .../study_plan/widget/presentation/StudyPlanWidgetFeature.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index bea3216c04..f52243e171 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -1,5 +1,7 @@ package org.hyperskill.app.study_plan.widget.presentation +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import org.hyperskill.app.analytic.domain.model.AnalyticEvent import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState @@ -8,8 +10,6 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection import org.hyperskill.app.track.domain.model.Track -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds object StudyPlanWidgetFeature { internal val STUDY_PLAN_FETCH_INTERVAL: Duration = 1.seconds From 19109edebca49ffef2a025090d74be9311299346 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 21:33:13 +0500 Subject: [PATCH 09/13] fix tests --- .../org/hyperskill/StudyPlanWidgetTest.kt | 92 +++++++++++++------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt index 952d3cf1de..24e5c190fa 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt @@ -2,11 +2,6 @@ package org.hyperskill import dev.icerock.moko.resources.PluralsResource import dev.icerock.moko.resources.StringResource -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.test.fail import org.hyperskill.app.core.view.mapper.DateFormatter import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.learning_activities.domain.model.LearningActivity @@ -16,12 +11,18 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection +import org.hyperskill.app.study_plan.domain.model.StudyPlanSectionType import org.hyperskill.app.study_plan.domain.model.StudyPlanStatus import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetReducer import org.hyperskill.app.study_plan.widget.presentation.firstVisibleSection import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewState import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewStateMapper +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail class StudyPlanWidgetTest { @@ -47,11 +48,11 @@ class StudyPlanWidgetTest { fun `Get first visible section works correctly`() { val studyPlan = studyPlanStub(id = 0, sections = listOf(0, 1, 2, 3, 4)) val studyPlanSections = listOf( - studyPlanSectionStub(id = 0, isVisible = false), - studyPlanSectionStub(id = 1, isVisible = true), - studyPlanSectionStub(id = 2, isVisible = false), - studyPlanSectionStub(id = 3, isVisible = true), - studyPlanSectionStub(id = 4, isVisible = false) + studyPlanSectionStub(id = 0, isVisible = false, type = null), + studyPlanSectionStub(id = 1, isVisible = true, type = null), + studyPlanSectionStub(id = 2, isVisible = false, type = null), + studyPlanSectionStub(id = 3, isVisible = true, type = null), + studyPlanSectionStub(id = 4, isVisible = false, type = null) ) val state = StudyPlanWidgetFeature.State( studyPlan = studyPlan, @@ -414,11 +415,11 @@ class StudyPlanWidgetTest { fun `Reload content in background should persist content status for first visible section`() { val studyPlan = studyPlanStub(id = 0, sections = listOf(0, 1, 2, 3, 4)) val studyPlanSections = listOf( - studyPlanSectionStub(id = 0, isVisible = false), - studyPlanSectionStub(id = 1, isVisible = true), - studyPlanSectionStub(id = 2, isVisible = false), - studyPlanSectionStub(id = 3, isVisible = true), - studyPlanSectionStub(id = 4, isVisible = false) + studyPlanSectionStub(id = 0, isVisible = false, type = null), + studyPlanSectionStub(id = 1, isVisible = true, type = null), + studyPlanSectionStub(id = 2, isVisible = false, type = null), + studyPlanSectionStub(id = 3, isVisible = true, type = null), + studyPlanSectionStub(id = 4, isVisible = false, type = null) ) val state = StudyPlanWidgetFeature.State( studyPlan = studyPlan, @@ -549,7 +550,7 @@ class StudyPlanWidgetTest { val expectedViewState = StudyPlanWidgetViewState.Content( listOf( sectionViewState( - section = studyPlanSectionStub(id = 0), + section = studyPlanSectionStub(id = 0, type = null), content = StudyPlanWidgetViewState.SectionContent.Content( listOf( studyPlanSectionItemStub( @@ -560,7 +561,7 @@ class StudyPlanWidgetTest { ) ), sectionViewState( - section = studyPlanSectionStub(id = 1), + section = studyPlanSectionStub(id = 1, type = null), content = StudyPlanWidgetViewState.SectionContent.Content( listOf( studyPlanSectionItemStub( @@ -579,7 +580,7 @@ class StudyPlanWidgetTest { studyPlan = studyPlanStub(id = 0, sections = listOf(0, 1)), studyPlanSections = mapOf( 0L to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = studyPlanSectionStub(id = 0, activities = listOf(0)), + studyPlanSection = studyPlanSectionStub(id = 0, activities = listOf(0), type = null), isExpanded = true, contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ), @@ -588,7 +589,8 @@ class StudyPlanWidgetTest { id = 1, activities = listOf(1), completedTopicsCount = 1, - topicsCount = 10 + topicsCount = 10, + type = null ), isExpanded = true, contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED @@ -623,6 +625,7 @@ class StudyPlanWidgetTest { val activityId = 0L val projectId = 1L val stageId = 2L + val sectionId = 3L val state = StudyPlanWidgetFeature.State( studyPlan = studyPlanStub(id = 0, projectId = projectId), activities = mapOf( @@ -631,9 +634,18 @@ class StudyPlanWidgetTest { isCurrent = true, type = LearningActivityType.IMPLEMENT_STAGE, targetType = LearningActivityTargetType.STAGE, - targetId = stageId + targetId = stageId, + sectionId = sectionId ) - ) + ), + studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) + .associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -670,6 +682,7 @@ class StudyPlanWidgetTest { fun `Click on learn topic learning activity should navigate to step screen`() { val activityId = 0L val stepId = 1L + val sectionId = 2L val state = StudyPlanWidgetFeature.State( studyPlan = studyPlanStub(id = 0), activities = mapOf( @@ -678,9 +691,18 @@ class StudyPlanWidgetTest { isCurrent = true, type = LearningActivityType.LEARN_TOPIC, targetType = LearningActivityTargetType.STEP, - targetId = stepId + targetId = stepId, + sectionId = sectionId ) - ) + ), + studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) + .associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -723,6 +745,7 @@ class StudyPlanWidgetTest { @Test fun `Click on implement stage activity with ide required should show unsupported modal`() { val activityId = 0L + val sectionId = 1L val state = StudyPlanWidgetFeature.State( studyPlan = studyPlanStub(id = 0, projectId = 1L), activities = mapOf( @@ -733,8 +756,17 @@ class StudyPlanWidgetTest { targetType = LearningActivityTargetType.STAGE, targetId = 1L, isIdeRequired = true, + sectionId = sectionId ) - ) + ), + studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) + .associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -781,7 +813,8 @@ class StudyPlanWidgetTest { topicsCount: Int = 0, completedTopicsCount: Int = 0, secondsToComplete: Float = 0f, - activities: List = emptyList() + activities: List = emptyList(), + type: StudyPlanSectionType? = StudyPlanSectionType.ROOT_TOPICS ) = StudyPlanSection( id = id, @@ -794,7 +827,8 @@ class StudyPlanWidgetTest { topicsCount = topicsCount, completedTopicsCount = completedTopicsCount, secondsToComplete = secondsToComplete, - activities = activities + activities = activities, + type = type ) private fun stubLearningActivity( @@ -805,7 +839,8 @@ class StudyPlanWidgetTest { targetType: LearningActivityTargetType = LearningActivityTargetType.STEP, isCurrent: Boolean = false, title: String = "", - isIdeRequired: Boolean = false + isIdeRequired: Boolean = false, + sectionId: Long? = null ) = LearningActivity( id = id, @@ -815,7 +850,8 @@ class StudyPlanWidgetTest { isCurrent = isCurrent, targetTypeValue = targetType.value, title = title, - isIdeRequired = isIdeRequired + isIdeRequired = isIdeRequired, + sectionId = sectionId ) private fun studyPlanSectionItemStub( From f333ef804d456dabd6e68918969e9782de618d64 Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 21:55:14 +0500 Subject: [PATCH 10/13] add tests --- .../org/hyperskill/StudyPlanWidgetTest.kt | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt index 24e5c190fa..5d97f1e639 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt @@ -775,6 +775,127 @@ class StudyPlanWidgetTest { assertContains(actions, StudyPlanWidgetFeature.Action.ViewAction.ShowStageImplementUnsupportedModal) } + @Test + fun `Activity with is_current = true will be next for root topics section`() { + val currentActivityId = 0L + val notCurrentActivityId = 1L + val sectionId = 2L + val state = StudyPlanWidgetFeature.State( + studyPlan = studyPlanStub(id = 0, projectId = 1L, sections = listOf(sectionId)), + activities = mapOf( + currentActivityId to stubLearningActivity( + currentActivityId, + type = LearningActivityType.LEARN_TOPIC, + targetType = LearningActivityTargetType.STEP, + targetId = 1L, + isCurrent = false, + sectionId = sectionId + ), + notCurrentActivityId to stubLearningActivity( + notCurrentActivityId, + type = LearningActivityType.LEARN_TOPIC, + targetType = LearningActivityTargetType.STEP, + targetId = 1L, + isCurrent = true, + sectionId = sectionId + ) + ), + studyPlanSections = listOf( + studyPlanSectionStub( + id = sectionId, + activities = listOf(currentActivityId, notCurrentActivityId) + ) + ) + .associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + }, + sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + val viewState = studyPlanWidgetViewStateMapper.map(state) + + val viewSectionItems = ( + (viewState as? StudyPlanWidgetViewState.Content) + ?.sections + ?.firstOrNull() + ?.content as? StudyPlanWidgetViewState.SectionContent.Content + )?.sectionItems ?: fail("Unexpected view state: $viewState") + + assertEquals( + StudyPlanWidgetViewState.SectionItemState.NEXT, + viewSectionItems.first { it.id == notCurrentActivityId }.state + ) + assertEquals( + StudyPlanWidgetViewState.SectionItemState.LOCKED, + viewSectionItems.first { it.id == currentActivityId }.state + ) + } + + @Test + fun `First activity will be next for not root topics section`() { + val firstActivityId = 0L + val secondActivityId = 1L + val sectionId = 2L + val state = StudyPlanWidgetFeature.State( + studyPlan = studyPlanStub(id = 0, projectId = 1L, sections = listOf(sectionId)), + activities = mapOf( + firstActivityId to stubLearningActivity( + firstActivityId, + type = LearningActivityType.LEARN_TOPIC, + targetType = LearningActivityTargetType.STEP, + targetId = 1L, + isCurrent = false, + sectionId = sectionId + ), + secondActivityId to stubLearningActivity( + secondActivityId, + type = LearningActivityType.LEARN_TOPIC, + targetType = LearningActivityTargetType.STEP, + targetId = 1L, + isCurrent = true, + sectionId = sectionId + ) + ), + studyPlanSections = listOf( + studyPlanSectionStub( + id = sectionId, + activities = listOf(firstActivityId, secondActivityId), + type = null + ) + ) + .associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + }, + sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + val viewState = studyPlanWidgetViewStateMapper.map(state) + + val viewSectionItems = ( + (viewState as? StudyPlanWidgetViewState.Content) + ?.sections + ?.firstOrNull() + ?.content as? StudyPlanWidgetViewState.SectionContent.Content + )?.sectionItems ?: fail("Unexpected view state: $viewState") + + assertEquals( + StudyPlanWidgetViewState.SectionItemState.NEXT, + viewSectionItems.first { it.id == firstActivityId }.state + ) + assertEquals( + StudyPlanWidgetViewState.SectionItemState.LOCKED, + viewSectionItems.first { it.id == secondActivityId }.state + ) + } + private fun sectionViewState( section: StudyPlanSection, content: StudyPlanWidgetViewState.SectionContent, From 06dfd23d7ec5e9ffee56ef28f644f6fa9ee72b5a Mon Sep 17 00:00:00 2001 From: vladkash Date: Thu, 27 Apr 2023 22:26:56 +0500 Subject: [PATCH 11/13] fixes --- .../widget/presentation/StateExtentions.kt | 2 +- .../presentation/StudyPlanWidgetReducer.kt | 23 ++++--- .../org/hyperskill/StudyPlanWidgetTest.kt | 68 +++++++++++-------- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt index 91b31ff710..bbd11e81ed 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt @@ -22,7 +22,7 @@ internal fun StudyPlanWidgetFeature.State.getNextActivityId(section: StudyPlanSe return getSectionActivities(section.id) .firstOrNull { if (section.isRootTopicsSection()) { - it.isCurrent + it.id == section.nextActivityId } else { it.state == LearningActivityState.TODO } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index e7e4d45ef8..25fefccfd0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -243,14 +243,21 @@ class StudyPlanWidgetReducer : StateReducer { } private fun paginateActivitiesToFetch(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = - section.studyPlanSection.activities.apply { - if (section.studyPlanSection.isRootTopicsSection() && - section.studyPlanSection.nextActivityId != null - ) { - dropWhile { it != section.studyPlanSection.nextActivityId } - - take(ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) - } + if (section.studyPlanSection.isRootTopicsSection() && + section.studyPlanSection.nextActivityId != null + ) { + val activities = section.studyPlanSection.activities + section.studyPlanSection.activities.dropWhile { + it != section.studyPlanSection.nextActivityId + }.dropLast( + if (activities.size > ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) { + activities.size - ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT + } else { + 0 + } + ) + } else { + section.studyPlanSection.activities } private fun handleActivityClicked(state: State, activityId: Long): StudyPlanWidgetReducerResult { diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt index 5d97f1e639..0445bc1507 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt @@ -638,14 +638,17 @@ class StudyPlanWidgetTest { sectionId = sectionId ) ), - studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) - .associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = false, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - } + studyPlanSections = listOf( + studyPlanSectionStub( + id = sectionId, activities = listOf(activityId), nextActivityId = activityId + ) + ).associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -695,7 +698,11 @@ class StudyPlanWidgetTest { sectionId = sectionId ) ), - studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) + studyPlanSections = listOf( + studyPlanSectionStub( + id = sectionId, activities = listOf(activityId), nextActivityId = activityId + ) + ) .associate { it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( studyPlanSection = it, @@ -759,14 +766,17 @@ class StudyPlanWidgetTest { sectionId = sectionId ) ), - studyPlanSections = listOf(studyPlanSectionStub(id = sectionId, activities = listOf(activityId))) - .associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = false, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - } + studyPlanSections = listOf( + studyPlanSectionStub( + id = sectionId, activities = listOf(activityId), nextActivityId = activityId + ) + ).associate { + it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = it, + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -776,23 +786,23 @@ class StudyPlanWidgetTest { } @Test - fun `Activity with is_current = true will be next for root topics section`() { - val currentActivityId = 0L - val notCurrentActivityId = 1L + fun `Activity with id = section next_activity_id will be next for root topics section`() { + val nextActivityId = 0L + val notNextActivityId = 1L val sectionId = 2L val state = StudyPlanWidgetFeature.State( studyPlan = studyPlanStub(id = 0, projectId = 1L, sections = listOf(sectionId)), activities = mapOf( - currentActivityId to stubLearningActivity( - currentActivityId, + notNextActivityId to stubLearningActivity( + notNextActivityId, type = LearningActivityType.LEARN_TOPIC, targetType = LearningActivityTargetType.STEP, targetId = 1L, isCurrent = false, sectionId = sectionId ), - notCurrentActivityId to stubLearningActivity( - notCurrentActivityId, + nextActivityId to stubLearningActivity( + nextActivityId, type = LearningActivityType.LEARN_TOPIC, targetType = LearningActivityTargetType.STEP, targetId = 1L, @@ -803,7 +813,8 @@ class StudyPlanWidgetTest { studyPlanSections = listOf( studyPlanSectionStub( id = sectionId, - activities = listOf(currentActivityId, notCurrentActivityId) + activities = listOf(nextActivityId, notNextActivityId), + nextActivityId = nextActivityId ) ) .associate { @@ -827,11 +838,11 @@ class StudyPlanWidgetTest { assertEquals( StudyPlanWidgetViewState.SectionItemState.NEXT, - viewSectionItems.first { it.id == notCurrentActivityId }.state + viewSectionItems.first { it.id == nextActivityId }.state ) assertEquals( StudyPlanWidgetViewState.SectionItemState.LOCKED, - viewSectionItems.first { it.id == currentActivityId }.state + viewSectionItems.first { it.id == notNextActivityId }.state ) } @@ -864,6 +875,7 @@ class StudyPlanWidgetTest { studyPlanSectionStub( id = sectionId, activities = listOf(firstActivityId, secondActivityId), + nextActivityId = null, type = null ) ) @@ -935,6 +947,7 @@ class StudyPlanWidgetTest { completedTopicsCount: Int = 0, secondsToComplete: Float = 0f, activities: List = emptyList(), + nextActivityId: Long? = null, type: StudyPlanSectionType? = StudyPlanSectionType.ROOT_TOPICS ) = StudyPlanSection( @@ -942,6 +955,7 @@ class StudyPlanWidgetTest { studyPlanId = 0, targetId = 0, targetType = "", + nextActivityId = nextActivityId, isVisible = isVisible, title = "", subtitle = "", From 6e80f68844b2adac1bf3531e5907dc4e799f4e36 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Sat, 29 Apr 2023 12:00:04 +0300 Subject: [PATCH 12/13] Simplify logic --- .../domain/model/LearningActivity.kt | 8 +-- .../domain/model/StudyPlanSection.kt | 3 -- .../widget/presentation/StateExtentions.kt | 54 ++++++++++++------- .../StudyPlanWidgetActionDispatcher.kt | 3 -- .../presentation/StudyPlanWidgetReducer.kt | 39 +++++++------- .../view/StudyPlanWidgetViewStateMapper.kt | 29 +++++----- .../org/hyperskill/StudyPlanWidgetTest.kt | 4 +- 7 files changed, 71 insertions(+), 69 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt index 96f71741b4..276af875d2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/learning_activities/domain/model/LearningActivity.kt @@ -2,7 +2,6 @@ package org.hyperskill.app.learning_activities.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient @Serializable data class LearningActivity( @@ -16,17 +15,12 @@ data class LearningActivity( private val targetTypeValue: String, @SerialName("type") private val typeValue: Int, - @SerialName("is_current") - val isCurrent: Boolean, @SerialName("title") val title: String = "", @SerialName("hypercoins_award") val hypercoinsAward: Int = 0, @SerialName("is_ide_required") - val isIdeRequired: Boolean = false, - - @Transient - var sectionId: Long? = null + val isIdeRequired: Boolean = false ) { val state: LearningActivityState? get() = LearningActivityState.getByValue(stateValue) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt index 0750d51fdc..567567850f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/domain/model/StudyPlanSection.kt @@ -35,6 +35,3 @@ data class StudyPlanSection( val type: StudyPlanSectionType? get() = StudyPlanSectionType.getByValue(typeValue) } - -fun StudyPlanSection.isRootTopicsSection(): Boolean = - type == StudyPlanSectionType.ROOT_TOPICS \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt index 35c628b961..ad80862a31 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StateExtentions.kt @@ -4,31 +4,49 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.study_plan.domain.model.StudyPlanSection import org.hyperskill.app.study_plan.domain.model.StudyPlanSectionType -import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection -internal fun StudyPlanWidgetFeature.State.firstSection(): StudyPlanSection? = +/** + * Returns true if the section is supported in the study plan, when it is visible and its type is supported. + * @see StudyPlanSectionType + */ +internal val StudyPlanSection.isSupportedInStudyPlan: Boolean + get() = isVisible && StudyPlanSectionType.supportedTypes().contains(type) + +/** + * Finds first supported section in the study plan. + * + * @return first [StudyPlanSection] if it is supported in the study plan, otherwise null. + * @see StudyPlanSection.isSupportedInStudyPlan + */ +internal fun StudyPlanWidgetFeature.State.getCurrentSection(): StudyPlanSection? = studyPlanSections.values .firstOrNull { it.studyPlanSection.isSupportedInStudyPlan } ?.studyPlanSection +/** + * Finds current activity in the study plan. If the current section is root topics, then the next activity is returned. + * Otherwise, the first activity with [LearningActivityState.TODO] state is returned. + * + * @return current [LearningActivity]. + */ +internal fun StudyPlanWidgetFeature.State.getCurrentActivity(): LearningActivity? = + getCurrentSection()?.let { section -> + getSectionActivities(section.id) + .firstOrNull { + if (section.type == StudyPlanSectionType.ROOT_TOPICS) { + it.id == section.nextActivityId + } else { + it.state == LearningActivityState.TODO + } + } + } + +/** + * @param sectionId target section id. + * @return list of [LearningActivity] for the given section with [sectionId]. + */ internal fun StudyPlanWidgetFeature.State.getSectionActivities(sectionId: Long): List = studyPlanSections[sectionId] ?.studyPlanSection ?.activities ?.mapNotNull { id -> activities[id] } ?: emptyList() - -internal val StudyPlanSection.isSupportedInStudyPlan: Boolean - get() = isVisible && StudyPlanSectionType.supportedTypes().contains(type) - -internal fun StudyPlanWidgetFeature.State.getNextActivityId(section: StudyPlanSection): Long? { - if (firstSection()?.id != section.id) return null - - return getSectionActivities(section.id) - .firstOrNull { - if (section.isRootTopicsSection()) { - it.id == section.nextActivityId - } else { - it.state == LearningActivityState.TODO - } - }?.id -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt index b297430983..c51ab4ab81 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt @@ -65,9 +65,6 @@ class StudyPlanWidgetActionDispatcher( studyPlanInteractor.getLearningActivities(action.activitiesIds, action.types, action.states) .onSuccess { learningActivities -> sentryInteractor.finishTransaction(action.sentryTransaction) - learningActivities.forEach { learningActivity -> - learningActivity.sectionId = action.sectionId - } onNewMessage( StudyPlanWidgetFeature.LearningActivitiesFetchResult.Success( action.sectionId, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index 624c6bf0d0..33994633c2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.study_plan.widget.presentation +import kotlin.math.min import org.hyperskill.app.learning_activities.domain.model.LearningActivityTargetType import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder @@ -11,8 +12,8 @@ import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedSectionHype import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalClickedGoToHomeScreenHyperskillAnalyticEvent import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalShownHyperskillAnalyticEvent +import org.hyperskill.app.study_plan.domain.model.StudyPlanSectionType import org.hyperskill.app.study_plan.domain.model.StudyPlanStatus -import org.hyperskill.app.study_plan.domain.model.isRootTopicsSection import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.Action import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.InternalAction import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.Message @@ -43,7 +44,7 @@ class StudyPlanWidgetReducer : StateReducer { state.copy( studyPlanSections = state.studyPlanSections.mapValues { (sectionId, sectionInfo) -> sectionInfo.copy( - contentStatus = if (sectionId == state.firstSection()?.id) { + contentStatus = if (sectionId == state.getCurrentSection()?.id) { sectionInfo.contentStatus } else { StudyPlanWidgetFeature.ContentStatus.IDLE @@ -214,9 +215,9 @@ class StudyPlanWidgetReducer : StateReducer { ) to setOf( InternalAction.FetchActivities( sectionId = message.sectionId, - activitiesIds = paginateActivitiesToFetch(section), + activitiesIds = getPaginatedActivitiesIds(section), sentryTransaction = HyperskillSentryTransactionBuilder.buildStudyPlanWidgetFetchLearningActivities( - isCurrentSection = message.sectionId == state.firstSection()?.id + isCurrentSection = message.sectionId == state.getCurrentSection()?.id ) ), InternalAction.LogAnalyticEvent( @@ -255,9 +256,9 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOfNotNull( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = paginateActivitiesToFetch(section), + activitiesIds = getPaginatedActivitiesIds(section), sentryTransaction = HyperskillSentryTransactionBuilder.buildStudyPlanWidgetFetchLearningActivities( - isCurrentSection = sectionId == state.firstSection()?.id + isCurrentSection = sectionId == state.getCurrentSection()?.id ) ), logAnalyticEventAction @@ -275,29 +276,23 @@ class StudyPlanWidgetReducer : StateReducer { } } - private fun paginateActivitiesToFetch(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = - if (section.studyPlanSection.isRootTopicsSection() && + private fun getPaginatedActivitiesIds(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = + if (section.studyPlanSection.type == StudyPlanSectionType.ROOT_TOPICS && section.studyPlanSection.nextActivityId != null ) { - val activities = section.studyPlanSection.activities - section.studyPlanSection.activities.dropWhile { - it != section.studyPlanSection.nextActivityId - }.dropLast( - if (activities.size > ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT) { - activities.size - ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT - } else { - 0 - } + // TODO: Test this + val startIndex = section.studyPlanSection.activities.indexOf(section.studyPlanSection.nextActivityId) + val endIndex = min( + startIndex + (ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT - 1), + section.studyPlanSection.activities.size - 1 ) + section.studyPlanSection.activities.slice(startIndex..endIndex) } else { section.studyPlanSection.activities } private fun handleActivityClicked(state: State, activityId: Long): StudyPlanWidgetReducerResult { val activity = state.activities[activityId] ?: return state to emptySet() - val section = state.studyPlanSections[activity.sectionId]?.studyPlanSection ?: return state to emptySet() - - if (state.getNextActivityId(section) != activityId) return state to emptySet() val logAnalyticEventAction = InternalAction.LogAnalyticEvent( StudyPlanClickedActivityHyperskillAnalyticEvent( @@ -308,6 +303,10 @@ class StudyPlanWidgetReducer : StateReducer { ) ) + if (activity.id != state.getCurrentActivity()?.id) { + return state to setOf(logAnalyticEventAction) + } + val viewAction = when (activity.type) { LearningActivityType.IMPLEMENT_STAGE -> { val projectId = state.studyPlan?.projectId diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt index 0588e4ff95..4af0bcc7ea 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/StudyPlanWidgetViewStateMapper.kt @@ -5,8 +5,8 @@ import org.hyperskill.app.core.view.mapper.DateFormatter import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature -import org.hyperskill.app.study_plan.widget.presentation.firstSection -import org.hyperskill.app.study_plan.widget.presentation.getNextActivityId +import org.hyperskill.app.study_plan.widget.presentation.getCurrentActivity +import org.hyperskill.app.study_plan.widget.presentation.getCurrentSection import org.hyperskill.app.study_plan.widget.presentation.getSectionActivities import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewState.SectionContent @@ -20,14 +20,15 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { } private fun getLoadedWidgetContent(state: StudyPlanWidgetFeature.State): StudyPlanWidgetViewState.Content { - val firstSectionId = state.firstSection()?.id + val currentSectionId = state.getCurrentSection()?.id + val currentActivityId = state.getCurrentActivity()?.id return StudyPlanWidgetViewState.Content( sections = state.studyPlan?.sections?.mapNotNull { sectionId -> val sectionInfo = state.studyPlanSections[sectionId] ?: return@mapNotNull null val section = sectionInfo.studyPlanSection - val shouldShowSectionStatistics = firstSectionId == section.id || sectionInfo.isExpanded + val shouldShowSectionStatistics = currentSectionId == section.id || sectionInfo.isExpanded StudyPlanWidgetViewState.Section( id = section.id, @@ -35,7 +36,8 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { subtitle = section.subtitle.takeIf { it.isNotEmpty() }, content = getSectionContent( state = state, - sectionInfo = sectionInfo + sectionInfo = sectionInfo, + currentActivityId = currentActivityId ), formattedTopicsCount = if (shouldShowSectionStatistics) { formatTopicsCount( @@ -60,7 +62,8 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { private fun getSectionContent( sectionInfo: StudyPlanWidgetFeature.StudyPlanSectionInfo, - state: StudyPlanWidgetFeature.State + state: StudyPlanWidgetFeature.State, + currentActivityId: Long? ): SectionContent = if (sectionInfo.isExpanded) { when (sectionInfo.contentStatus) { @@ -70,20 +73,14 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { if (activities.isEmpty()) { SectionContent.Loading } else { - getContent( - activities = activities, - nextActivityId = state.getNextActivityId(sectionInfo.studyPlanSection) - ) + getContent(activities, currentActivityId) } } StudyPlanWidgetFeature.ContentStatus.ERROR -> SectionContent.Error StudyPlanWidgetFeature.ContentStatus.LOADED -> { val activities = state.getSectionActivities(sectionInfo.studyPlanSection.id) if (activities.isNotEmpty()) { - getContent( - activities = activities, - nextActivityId = state.getNextActivityId(section = sectionInfo.studyPlanSection) - ) + getContent(activities, currentActivityId) } else { SectionContent.Error } @@ -93,14 +90,14 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: DateFormatter) { SectionContent.Collapsed } - private fun getContent(activities: List, nextActivityId: Long?): SectionContent.Content = + private fun getContent(activities: List, currentActivityId: Long?): SectionContent.Content = SectionContent.Content( sectionItems = activities.map { activity -> StudyPlanWidgetViewState.SectionItem( id = activity.id, title = activity.title.ifBlank { activity.id.toString() }, state = when (activity.state) { - LearningActivityState.TODO -> if (activity.id == nextActivityId) { + LearningActivityState.TODO -> if (activity.id == currentActivityId) { StudyPlanWidgetViewState.SectionItemState.NEXT } else { StudyPlanWidgetViewState.SectionItemState.LOCKED diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt index c05fb5fcc7..4656655b13 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt @@ -25,7 +25,7 @@ import org.hyperskill.app.study_plan.domain.model.StudyPlanSectionType import org.hyperskill.app.study_plan.domain.model.StudyPlanStatus import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetReducer -import org.hyperskill.app.study_plan.widget.presentation.firstSection +import org.hyperskill.app.study_plan.widget.presentation.getCurrentSection import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewState import org.hyperskill.app.study_plan.widget.view.StudyPlanWidgetViewStateMapper @@ -72,7 +72,7 @@ class StudyPlanWidgetTest { } ) - assertEquals(studyPlanSections.first { it.id == expectedSectionId }, state.firstSection()) + assertEquals(studyPlanSections.first { it.id == expectedSectionId }, state.getCurrentSection()) } @Test From 7606b96c5b772cff2886a8b7bface64223584522 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Sat, 29 Apr 2023 17:42:25 +0300 Subject: [PATCH 13/13] Update tests --- .../presentation/StudyPlanWidgetReducer.kt | 15 +- .../org/hyperskill/StudyPlanWidgetTest.kt | 329 +++++++++++------- 2 files changed, 208 insertions(+), 136 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index 33994633c2..8376334ff1 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -1,5 +1,6 @@ package org.hyperskill.app.study_plan.widget.presentation +import kotlin.math.max import kotlin.math.min import org.hyperskill.app.learning_activities.domain.model.LearningActivityTargetType import org.hyperskill.app.learning_activities.domain.model.LearningActivityType @@ -25,7 +26,7 @@ internal typealias StudyPlanWidgetReducerResult = Pair> class StudyPlanWidgetReducer : StateReducer { companion object { - const val ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT = 10 + const val SECTION_ROOT_TOPICS_PAGE_SIZE = 10 } override fun reduce(state: State, message: Message): StudyPlanWidgetReducerResult = @@ -276,16 +277,14 @@ class StudyPlanWidgetReducer : StateReducer { } } - private fun getPaginatedActivitiesIds(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = + internal fun getPaginatedActivitiesIds(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = if (section.studyPlanSection.type == StudyPlanSectionType.ROOT_TOPICS && section.studyPlanSection.nextActivityId != null ) { - // TODO: Test this - val startIndex = section.studyPlanSection.activities.indexOf(section.studyPlanSection.nextActivityId) - val endIndex = min( - startIndex + (ROOT_TOPICS_SECTION_VISIBLE_ACTIVITIES_COUNT - 1), - section.studyPlanSection.activities.size - 1 - ) + val startIndex = + max(0, section.studyPlanSection.activities.indexOf(section.studyPlanSection.nextActivityId)) + val endIndex = + min(startIndex + (SECTION_ROOT_TOPICS_PAGE_SIZE - 1), section.studyPlanSection.activities.size - 1) section.studyPlanSection.activities.slice(startIndex..endIndex) } else { section.studyPlanSection.activities diff --git a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt index 4656655b13..a4bd665c6e 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/StudyPlanWidgetTest.kt @@ -181,6 +181,16 @@ class StudyPlanWidgetTest { @Test fun `Loaded sections should be filtered by supportance`() { + assertEquals( + setOf( + StudyPlanSectionType.STAGE, + StudyPlanSectionType.EXTRA_TOPICS, + StudyPlanSectionType.ROOT_TOPICS + ), + StudyPlanSectionType.supportedTypes(), + "Test should be updated according to new supported types" + ) + val visibleUnsupportedSection = studyPlanSectionStub( id = 0, isVisible = true, @@ -407,7 +417,12 @@ class StudyPlanWidgetTest { sectionViewState( section = section.studyPlanSection, content = StudyPlanWidgetViewState.SectionContent.Content( - listOf(studyPlanSectionItemStub(activityId)) + listOf( + studyPlanSectionItemStub( + activityId, + state = StudyPlanWidgetViewState.SectionItemState.NEXT + ) + ) ) ) ) @@ -440,7 +455,12 @@ class StudyPlanWidgetTest { sectionViewState( section = section.studyPlanSection, content = StudyPlanWidgetViewState.SectionContent.Content( - listOf(studyPlanSectionItemStub(activityId)) + listOf( + studyPlanSectionItemStub( + activityId, + state = StudyPlanWidgetViewState.SectionItemState.NEXT + ) + ) ) ) ) @@ -493,7 +513,7 @@ class StudyPlanWidgetTest { studyPlanSectionItemStub( activityId = 0, title = "Activity 1", - state = StudyPlanWidgetViewState.SectionItemState.LOCKED + state = StudyPlanWidgetViewState.SectionItemState.NEXT ) ) ) @@ -531,7 +551,7 @@ class StudyPlanWidgetTest { studyPlanSectionItemStub( activityId = 0, title = "0", - state = StudyPlanWidgetViewState.SectionItemState.LOCKED + state = StudyPlanWidgetViewState.SectionItemState.NEXT ) ) ) @@ -641,7 +661,7 @@ class StudyPlanWidgetTest { ) ), activities = mapOf( - 0L to stubLearningActivity(id = 0, isCurrent = true), + 0L to stubLearningActivity(id = 0), 1L to stubLearningActivity(id = 1) ), sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED @@ -653,10 +673,24 @@ class StudyPlanWidgetTest { @Test fun `Click on non current learning activity should do nothing`() { - val activityId = 0L val state = StudyPlanWidgetFeature.State( - activities = mapOf(activityId to stubLearningActivity(activityId, isCurrent = false)) + studyPlan = studyPlanStub(id = 0, sections = listOf(0, 1)), + studyPlanSections = mapOf( + 0L to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = 0, activities = listOf(0)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ), + 1L to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = 1, activities = listOf(1)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), + activities = mapOf(0L to stubLearningActivity(id = 0), 1L to stubLearningActivity(id = 1)), + sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) + val activityId = 1L val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -672,29 +706,24 @@ class StudyPlanWidgetTest { val projectId = 1L val stageId = 2L val sectionId = 3L + val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0, projectId = projectId), + studyPlan = studyPlanStub(id = 0, projectId = projectId, sections = listOf(sectionId)), + studyPlanSections = mapOf( + sectionId to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = sectionId, activities = listOf(activityId)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), activities = mapOf( activityId to stubLearningActivity( activityId, - isCurrent = true, type = LearningActivityType.IMPLEMENT_STAGE, targetType = LearningActivityTargetType.STAGE, - targetId = stageId, - sectionId = sectionId - ) - ), - studyPlanSections = listOf( - studyPlanSectionStub( - id = sectionId, activities = listOf(activityId), nextActivityId = activityId - ) - ).associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = false, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + targetId = stageId ) - } + ) ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -711,11 +740,17 @@ class StudyPlanWidgetTest { fun `Click on stage implement learning activity with non stage target should do nothing`() { val activityId = 0L val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0), + studyPlan = studyPlanStub(id = 0, sections = listOf(0)), + studyPlanSections = mapOf( + 0L to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = 0, activities = listOf(activityId)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), activities = mapOf( activityId to stubLearningActivity( activityId, - isCurrent = true, type = LearningActivityType.IMPLEMENT_STAGE, targetType = LearningActivityTargetType.STEP, targetId = 1L @@ -737,29 +772,22 @@ class StudyPlanWidgetTest { val stepId = 1L val sectionId = 2L val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0), + studyPlan = studyPlanStub(id = 0, sections = listOf(sectionId)), + studyPlanSections = mapOf( + sectionId to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = sectionId, activities = listOf(activityId)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), activities = mapOf( activityId to stubLearningActivity( activityId, - isCurrent = true, type = LearningActivityType.LEARN_TOPIC, targetType = LearningActivityTargetType.STEP, - targetId = stepId, - sectionId = sectionId - ) - ), - studyPlanSections = listOf( - studyPlanSectionStub( - id = sectionId, activities = listOf(activityId), nextActivityId = activityId + targetId = stepId ) ) - .associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = false, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - } ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -782,11 +810,17 @@ class StudyPlanWidgetTest { fun `Click on learn topic learning activity with non step target should do nothing`() { val activityId = 0L val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0), + studyPlan = studyPlanStub(id = 0, sections = listOf(0)), + studyPlanSections = mapOf( + 0L to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = 0, activities = listOf(activityId)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), activities = mapOf( activityId to stubLearningActivity( activityId, - isCurrent = true, type = LearningActivityType.LEARN_TOPIC, targetType = LearningActivityTargetType.STAGE, targetId = 1L @@ -808,28 +842,22 @@ class StudyPlanWidgetTest { val sectionId = 1L val state = StudyPlanWidgetFeature.State( studyPlan = studyPlanStub(id = 0, projectId = 1L), + studyPlanSections = mapOf( + sectionId to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub(id = sectionId, activities = listOf(activityId)), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + ), activities = mapOf( activityId to stubLearningActivity( activityId, - isCurrent = true, type = LearningActivityType.IMPLEMENT_STAGE, targetType = LearningActivityTargetType.STAGE, targetId = 1L, - isIdeRequired = true, - sectionId = sectionId - ) - ), - studyPlanSections = listOf( - studyPlanSectionStub( - id = sectionId, activities = listOf(activityId), nextActivityId = activityId + isIdeRequired = true ) - ).associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = false, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - } + ) ) val (newState, actions) = reducer.reduce(state, StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) @@ -883,44 +911,110 @@ class StudyPlanWidgetTest { } @Test - fun `Activity with id = section next_activity_id will be next for root topics section`() { + fun `Get paginated activities ids in non root topics section`() { + val expectedActivitiesIds = listOf(1L, 2L, 3L, 4L, 5L) + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.STAGE, + activities = expectedActivitiesIds + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + } + + @Test + fun `Get paginated activities ids in empty root topics section`() { + val expectedActivitiesIds = emptyList() + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.ROOT_TOPICS, + activities = expectedActivitiesIds + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + } + + @Test + fun `Get paginated activities ids in root topics section when end index greater than size`() { + val expectedActivitiesIds = listOf(3L, 4L, 5L) + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.ROOT_TOPICS, + activities = (0L..5L).toList(), + nextActivityId = 3L + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + } + + @Test + fun `Get paginated activities ids in root topics section returns correct result page size`() { + val expectedActivitiesIds = (5L..14L).toList() + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.ROOT_TOPICS, + activities = (0L..100L).toList(), + nextActivityId = 5L + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + } + + @Test + fun `Get paginated activities ids in root topics section when activities not contains next activity id`() { + val expectedActivitiesIds = (0L..4L).toList() + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.ROOT_TOPICS, + activities = expectedActivitiesIds, + nextActivityId = 10L + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + } + + @Test + fun `Activity with id section next_activity_id in ViewState will be next for root topics section`() { val nextActivityId = 0L val notNextActivityId = 1L val sectionId = 2L val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0, projectId = 1L, sections = listOf(sectionId)), - activities = mapOf( - notNextActivityId to stubLearningActivity( - notNextActivityId, - type = LearningActivityType.LEARN_TOPIC, - targetType = LearningActivityTargetType.STEP, - targetId = 1L, - isCurrent = false, - sectionId = sectionId - ), - nextActivityId to stubLearningActivity( - nextActivityId, - type = LearningActivityType.LEARN_TOPIC, - targetType = LearningActivityTargetType.STEP, - targetId = 1L, - isCurrent = true, - sectionId = sectionId + studyPlan = studyPlanStub(id = 0, sections = listOf(sectionId)), + studyPlanSections = mapOf( + sectionId to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = sectionId, + activities = listOf(nextActivityId, notNextActivityId), + nextActivityId = nextActivityId + ), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) ), - studyPlanSections = listOf( - studyPlanSectionStub( - id = sectionId, - activities = listOf(nextActivityId, notNextActivityId), - nextActivityId = nextActivityId - ) - ) - .associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = true, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - }, + activities = mapOf( + notNextActivityId to stubLearningActivity(notNextActivityId), + nextActivityId to stubLearningActivity(nextActivityId) + ), sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) @@ -944,44 +1038,27 @@ class StudyPlanWidgetTest { } @Test - fun `First activity will be next for not root topics section`() { + fun `First activity in ViewState will be next for not root topics section`() { val firstActivityId = 0L val secondActivityId = 1L val sectionId = 2L val state = StudyPlanWidgetFeature.State( - studyPlan = studyPlanStub(id = 0, projectId = 1L, sections = listOf(sectionId)), - activities = mapOf( - firstActivityId to stubLearningActivity( - firstActivityId, - type = LearningActivityType.LEARN_TOPIC, - targetType = LearningActivityTargetType.STEP, - targetId = 1L, - isCurrent = false, - sectionId = sectionId - ), - secondActivityId to stubLearningActivity( - secondActivityId, - type = LearningActivityType.LEARN_TOPIC, - targetType = LearningActivityTargetType.STEP, - targetId = 1L, - isCurrent = true, - sectionId = sectionId + studyPlan = studyPlanStub(id = 0, sections = listOf(sectionId)), + studyPlanSections = mapOf( + sectionId to StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = sectionId, + activities = listOf(firstActivityId, secondActivityId), + nextActivityId = null + ), + isExpanded = true, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) ), - studyPlanSections = listOf( - studyPlanSectionStub( - id = sectionId, - activities = listOf(firstActivityId, secondActivityId), - nextActivityId = null - ) - ) - .associate { - it.id to StudyPlanWidgetFeature.StudyPlanSectionInfo( - studyPlanSection = it, - isExpanded = true, - contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED - ) - }, + activities = mapOf( + firstActivityId to stubLearningActivity(firstActivityId), + secondActivityId to stubLearningActivity(secondActivityId) + ), sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) @@ -1068,21 +1145,17 @@ class StudyPlanWidgetTest { targetId: Long = 0L, type: LearningActivityType = LearningActivityType.LEARN_TOPIC, targetType: LearningActivityTargetType = LearningActivityTargetType.STEP, - isCurrent: Boolean = false, title: String = "", - isIdeRequired: Boolean = false, - sectionId: Long? = null + isIdeRequired: Boolean = false ) = LearningActivity( id = id, stateValue = state.value, targetId = targetId, typeValue = type.value, - isCurrent = isCurrent, targetTypeValue = targetType.value, title = title, - isIdeRequired = isIdeRequired, - sectionId = sectionId + isIdeRequired = isIdeRequired ) private fun studyPlanSectionItemStub(