From cf051460fe9d01bf18c9c908c896eecd7a756715 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 26 Jul 2023 13:50:16 +0400 Subject: [PATCH] Support nextLearningActivity widget on Android home screen (#598) ^ALTAPPS-863 Needs testing --- .../DataLoadingErrorAdapterDelegate.kt | 21 ++++ .../home/view/ui/fragment/HomeFragment.kt | 46 +++++++- .../delegate/NextLearningActivityDelegate.kt | 102 ++++++++++++++++++ ...earningActivityLoadingErrorRecyclerItem.kt | 5 + .../adapter/ActivityLoadingAdapterDelegate.kt | 19 ++++ .../StudyPlanActivityAdapterDelegate.kt | 8 +- ...LearningActivityTargetViewActionHandler.kt | 55 ++++++++++ .../delegate/StudyPlanWidgetDelegate.kt | 49 ++++----- .../study_plan/fragment/StudyPlanFragment.kt | 49 ++------- .../study_plan/model/StudyPlanRecyclerItem.kt | 15 ++- .../src/main/res/layout/fragment_home.xml | 4 +- .../layout_home_topics_to_discover_next.xml | 34 ------ .../layout/layout_next_learning_activity.xml | 31 ++++++ .../Views/NextLearningActivityView.swift | 1 + .../NextLearningActivityWidgetFeature.kt | 1 + ...xtLearningActivityWidgetViewStateMapper.kt | 1 + 16 files changed, 323 insertions(+), 118 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/adapter/DataLoadingErrorAdapterDelegate.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/delegate/NextLearningActivityDelegate.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/model/NextLearningActivityLoadingErrorRecyclerItem.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/ActivityLoadingAdapterDelegate.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/LearningActivityTargetViewActionHandler.kt delete mode 100644 androidHyperskillApp/src/main/res/layout/layout_home_topics_to_discover_next.xml create mode 100644 androidHyperskillApp/src/main/res/layout/layout_next_learning_activity.xml diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/adapter/DataLoadingErrorAdapterDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/adapter/DataLoadingErrorAdapterDelegate.kt new file mode 100644 index 0000000000..651a3e4ff8 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/view/ui/adapter/DataLoadingErrorAdapterDelegate.kt @@ -0,0 +1,21 @@ +package org.hyperskill.app.android.core.view.ui.adapter + +import org.hyperskill.app.android.R +import org.hyperskill.app.android.databinding.WidgetDataLoadingErrorBinding +import ru.nobird.android.ui.adapterdelegates.AdapterDelegate +import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder +import ru.nobird.android.ui.adapterdelegates.dsl.adapterDelegate + +object DataLoadingErrorAdapterDelegate { + // adapterDelegate function is used instead of creating a special class + // because class casting requires a reified type + inline operator fun invoke( + crossinline onReloadClick: (DT) -> Unit + ): AdapterDelegate> = + adapterDelegate(R.layout.widget_data_loading_error) { + val viewBinding = WidgetDataLoadingErrorBinding.bind(itemView) + viewBinding.reloadButton.setOnClickListener { + item?.let(onReloadClick) + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt index e0b5e0e77d..9a3c709d55 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/home/view/ui/fragment/HomeFragment.kt @@ -20,8 +20,10 @@ import org.hyperskill.app.android.core.view.ui.updateIsRefreshing import org.hyperskill.app.android.databinding.FragmentHomeBinding import org.hyperskill.app.android.gamification_toolbar.view.ui.delegate.GamificationToolbarDelegate import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter +import org.hyperskill.app.android.next_learning_activity.view.delegate.NextLearningActivityDelegate import org.hyperskill.app.android.problem_of_day.view.delegate.ProblemOfDayCardFormDelegate import org.hyperskill.app.android.problems_limit.view.ui.delegate.ProblemsLimitDelegate +import org.hyperskill.app.android.stage_implementation.view.dialog.UnsupportedStageBottomSheet import org.hyperskill.app.android.step.view.screen.StepScreen import org.hyperskill.app.android.topics_repetitions.view.delegate.TopicsRepetitionCardFormDelegate import org.hyperskill.app.android.topics_repetitions.view.screen.TopicsRepetitionScreen @@ -39,7 +41,8 @@ import ru.nobird.app.presentation.redux.container.ReduxView class HomeFragment : Fragment(R.layout.fragment_home), - ReduxView { + ReduxView, + UnsupportedStageBottomSheet.Callback { companion object { fun newInstance(): Fragment = HomeFragment() @@ -66,6 +69,15 @@ class HomeFragment : private val topicsRepetitionDelegate: TopicsRepetitionCardFormDelegate by lazy(LazyThreadSafetyMode.NONE) { TopicsRepetitionCardFormDelegate() } + + private val nextLearningActivityDelegate: NextLearningActivityDelegate by lazy(LazyThreadSafetyMode.NONE) { + NextLearningActivityDelegate(requireContext()) { nextLearningActivityMessage -> + homeViewModel.onNewMessage( + HomeFeature.Message.NextLearningActivityWidgetMessage(nextLearningActivityMessage) + ) + } + } + private var gamificationToolbarDelegate: GamificationToolbarDelegate? = null private val onForegroundObserver = @@ -91,6 +103,7 @@ class HomeFragment : initGamificationToolbarDelegate() initProblemsLimitDelegate() problemOfDayCardFormDelegate.setup(viewBinding.homeScreenProblemOfDayCard) + nextLearningActivityDelegate.setup(requireContext(), viewBinding.homeNextLearningActivity) with(viewBinding) { homeScreenSwipeRefreshLayout.setHyperskillColors() homeScreenSwipeRefreshLayout.setOnRefreshListener { @@ -154,7 +167,7 @@ class HomeFragment : viewBinding.homeScreenKeepPracticingTextView, viewBinding.homeScreenProblemOfDayCard.root, viewBinding.homeScreenTopicsRepetitionCard.root, - viewBinding.homeScreenKeepLearningInWebButton, + viewBinding.homeScreenKeepLearningInWebButton ) } } @@ -198,9 +211,20 @@ class HomeFragment : mainScreenRouter = mainScreenRouter, router = requireRouter() ) - else -> { + is HomeFeature.Action.ViewAction.NavigateTo.StepScreen -> { + requireRouter().navigateTo( + StepScreen(action.stepRoute) + ) + } + is HomeFeature.Action.ViewAction.ProblemsLimitViewAction -> { // no op } + is HomeFeature.Action.ViewAction.NextLearningActivityWidgetViewAction -> { + nextLearningActivityDelegate.handleAction( + fragment = this, + action = action.viewAction + ) + } } } @@ -220,6 +244,7 @@ class HomeFragment : problemsLimitViewStateMapper?.let { mapper -> problemsLimitDelegate?.render(mapper.mapState(state.problemsLimitState)) } + nextLearningActivityDelegate.render(state.nextLearningActivityWidgetState) } private fun renderSwipeRefresh(state: HomeFeature.State) { @@ -269,4 +294,19 @@ class HomeFragment : ) } } + + // UnsupportedStageBottomSheet.Callback methods + override fun onShow() { + homeViewModel.onNewMessage(HomeFeature.Message.StageImplementUnsupportedModalShownEventMessage) + } + + override fun onDismiss() { + homeViewModel.onNewMessage(HomeFeature.Message.StageImplementUnsupportedModalHiddenEventMessage) + } + + override fun onHomeClick() { + homeViewModel.onNewMessage(HomeFeature.Message.StageImplementUnsupportedModalGoToHomeClicked) + childFragmentManager + .dismissDialogFragmentIfExists(UnsupportedStageBottomSheet.TAG) + } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/delegate/NextLearningActivityDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/delegate/NextLearningActivityDelegate.kt new file mode 100644 index 0000000000..82a9b213ae --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/delegate/NextLearningActivityDelegate.kt @@ -0,0 +1,102 @@ +package org.hyperskill.app.android.next_learning_activity.view.delegate + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.view.ui.adapter.DataLoadingErrorAdapterDelegate +import org.hyperskill.app.android.core.view.ui.adapter.decoration.HorizontalMarginItemDecoration +import org.hyperskill.app.android.databinding.LayoutNextLearningActivityBinding +import org.hyperskill.app.android.next_learning_activity.view.model.NextLearningActivityLoadingErrorRecyclerItem +import org.hyperskill.app.android.stage_implementation.view.dialog.UnsupportedStageBottomSheet +import org.hyperskill.app.android.study_plan.adapter.ActivityLoadingAdapterDelegate +import org.hyperskill.app.android.study_plan.adapter.StudyPlanActivityAdapterDelegate +import org.hyperskill.app.android.study_plan.delegate.LearningActivityTargetViewActionHandler +import org.hyperskill.app.android.study_plan.model.StudyPlanRecyclerItem +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature.ViewState.Content +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature.ViewState.Empty +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature.ViewState.Idle +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature.ViewState.Loading +import org.hyperskill.app.next_learning_activity_widget.presentation.NextLearningActivityWidgetFeature.ViewState.NetworkError +import org.hyperskill.app.next_learning_activity_widget.view.mapper.NextLearningActivityWidgetViewStateMapper +import ru.nobird.android.ui.adapters.DefaultDelegateAdapter + +class NextLearningActivityDelegate( + context: Context, + onNewMessage: (NextLearningActivityWidgetFeature.Message) -> Unit +) { + + private val nextLearningActivityAdapter by lazy(LazyThreadSafetyMode.NONE) { + DefaultDelegateAdapter().apply { + addDelegate( + StudyPlanActivityAdapterDelegate { + onNewMessage(NextLearningActivityWidgetFeature.Message.NextLearningActivityClicked) + } + ) + addDelegate(ActivityLoadingAdapterDelegate()) + addDelegate( + DataLoadingErrorAdapterDelegate { + onNewMessage(NextLearningActivityWidgetFeature.Message.RetryContentLoading) + } + ) + } + } + + private val nextLearningActivityTitleTextColor: Int = + ContextCompat.getColor(context, StudyPlanRecyclerItem.Activity.activeTextColorRes) + private val nextLearningActivityEndIcon: Drawable? = + ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.nextActivityIconRes) + + fun setup(context: Context, viewBinding: LayoutNextLearningActivityBinding) { + with(viewBinding) { + with(homeNextLearningActivityRecycler) { + adapter = nextLearningActivityAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + addItemDecoration( + HorizontalMarginItemDecoration( + horizontalMargin = context.resources.getDimensionPixelOffset(R.dimen.screen_horizontal_padding) + ) + ) + } + } + } + + fun render(state: NextLearningActivityWidgetFeature.State) { + val viewState = NextLearningActivityWidgetViewStateMapper.map(state.contentState) + nextLearningActivityAdapter.items = when (viewState) { + Idle, Empty -> emptyList() + Loading -> listOf(StudyPlanRecyclerItem.ActivityLoading(0, 0)) + is Content -> listOf(mapContentToRecyclerItem(viewState)) + NetworkError -> listOf(NextLearningActivityLoadingErrorRecyclerItem) + } + } + + private fun mapContentToRecyclerItem(content: Content): StudyPlanRecyclerItem.Activity = + StudyPlanRecyclerItem.Activity( + id = content.id, + title = content.title, + subtitle = content.subtitle, + titleTextColor = nextLearningActivityTitleTextColor, + progress = content.progress, + formattedProgress = content.formattedProgress, + endIcon = nextLearningActivityEndIcon, + isClickable = true, + isIdeRequired = content.isIdeRequired + ) + + fun handleAction( + fragment: TFragment, + action: NextLearningActivityWidgetFeature.Action.ViewAction + ) where TFragment : Fragment, TFragment : UnsupportedStageBottomSheet.Callback { + when (action) { + is NextLearningActivityWidgetFeature.Action.ViewAction.NavigateTo.LearningActivityTarget -> + LearningActivityTargetViewActionHandler.handle( + fragment = fragment, + viewAction = action.viewAction + ) + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/model/NextLearningActivityLoadingErrorRecyclerItem.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/model/NextLearningActivityLoadingErrorRecyclerItem.kt new file mode 100644 index 0000000000..522bda52b0 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/next_learning_activity/view/model/NextLearningActivityLoadingErrorRecyclerItem.kt @@ -0,0 +1,5 @@ +package org.hyperskill.app.android.next_learning_activity.view.model + +import org.hyperskill.app.android.study_plan.model.StudyPlanRecyclerItem + +object NextLearningActivityLoadingErrorRecyclerItem : StudyPlanRecyclerItem \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/ActivityLoadingAdapterDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/ActivityLoadingAdapterDelegate.kt new file mode 100644 index 0000000000..ae9c9f93eb --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/ActivityLoadingAdapterDelegate.kt @@ -0,0 +1,19 @@ +package org.hyperskill.app.android.study_plan.adapter + +import android.view.ViewGroup +import org.hyperskill.app.android.R +import org.hyperskill.app.android.study_plan.model.StudyPlanRecyclerItem +import ru.nobird.android.ui.adapterdelegates.AdapterDelegate +import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder + +class ActivityLoadingAdapterDelegate : AdapterDelegate< + StudyPlanRecyclerItem, + DelegateViewHolder>() { + override fun isForViewType(position: Int, data: StudyPlanRecyclerItem): Boolean = + data is StudyPlanRecyclerItem.ActivityLoading + + override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = + object : DelegateViewHolder( + createView(parent, R.layout.item_study_plan_activities_loading) + ) {} +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt index 7869041811..40dde5120d 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt @@ -8,14 +8,12 @@ import androidx.core.view.updateLayoutParams import org.hyperskill.app.android.R import org.hyperskill.app.android.databinding.ItemStudyPlanActivityBinding import org.hyperskill.app.android.study_plan.model.StudyPlanRecyclerItem -import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder class StudyPlanActivityAdapterDelegate( - private val onNewMessage: (StudyPlanWidgetFeature.Message) -> Unit -) : - AdapterDelegate>() { + private val onActivityClicked: (Long) -> Unit +) : AdapterDelegate>() { override fun isForViewType(position: Int, data: StudyPlanRecyclerItem): Boolean = data is StudyPlanRecyclerItem.Activity @@ -31,7 +29,7 @@ class StudyPlanActivityAdapterDelegate( itemView.setOnClickListener { val activityId = (itemData as? StudyPlanRecyclerItem.Activity)?.id if (activityId != null) { - onNewMessage(StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) + onActivityClicked(activityId) } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/LearningActivityTargetViewActionHandler.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/LearningActivityTargetViewActionHandler.kt new file mode 100644 index 0000000000..39f61c16f7 --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/LearningActivityTargetViewActionHandler.kt @@ -0,0 +1,55 @@ +package org.hyperskill.app.android.study_plan.delegate + +import androidx.fragment.app.Fragment +import org.hyperskill.app.android.core.view.ui.navigation.requireRouter +import org.hyperskill.app.android.projects_selection.list.navigation.ProjectSelectionListScreen +import org.hyperskill.app.android.stage_implementation.view.dialog.UnsupportedStageBottomSheet +import org.hyperskill.app.android.stage_implementation.view.navigation.StageImplementationScreen +import org.hyperskill.app.android.step.view.screen.StepScreen +import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen +import org.hyperskill.app.learning_activities.presentation.model.LearningActivityTargetViewAction +import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListParams +import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams +import ru.nobird.android.view.base.ui.extension.showIfNotExists + +object LearningActivityTargetViewActionHandler { + fun handle( + fragment: TFragment, + viewAction: LearningActivityTargetViewAction + ) where TFragment : Fragment, TFragment : UnsupportedStageBottomSheet.Callback { + when (viewAction) { + is LearningActivityTargetViewAction.NavigateTo.SelectProject -> { + fragment.requireRouter().navigateTo( + ProjectSelectionListScreen( + ProjectSelectionListParams( + trackId = viewAction.trackId, + isNewUserMode = false + ) + ) + ) + } + LearningActivityTargetViewAction.NavigateTo.SelectTrack -> { + fragment.requireRouter().navigateTo( + TrackSelectionListScreen( + TrackSelectionListParams(isNewUserMode = false) + ) + ) + } + is LearningActivityTargetViewAction.NavigateTo.StageImplement -> { + fragment.requireRouter().navigateTo( + StageImplementationScreen( + projectId = viewAction.projectId, + stageId = viewAction.stageId + ) + ) + } + is LearningActivityTargetViewAction.NavigateTo.Step -> { + fragment.requireRouter().navigateTo(StepScreen(viewAction.stepRoute)) + } + LearningActivityTargetViewAction.ShowStageImplementIDERequiredModal -> { + UnsupportedStageBottomSheet.newInstance() + .showIfNotExists(fragment.childFragmentManager, UnsupportedStageBottomSheet.TAG) + } + } + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt index adb0e70c72..2df5044183 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt @@ -6,9 +6,10 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import org.hyperskill.app.android.R +import org.hyperskill.app.android.core.view.ui.adapter.DataLoadingErrorAdapterDelegate import org.hyperskill.app.android.core.view.ui.adapter.decoration.itemDecoration import org.hyperskill.app.android.databinding.ErrorNoConnectionWithButtonBinding -import org.hyperskill.app.android.databinding.WidgetDataLoadingErrorBinding +import org.hyperskill.app.android.study_plan.adapter.ActivityLoadingAdapterDelegate import org.hyperskill.app.android.study_plan.adapter.StudyPlanActivityAdapterDelegate import org.hyperskill.app.android.study_plan.adapter.StudyPlanItemAnimator import org.hyperskill.app.android.study_plan.adapter.StudyPlanSectionAdapterDelegate @@ -32,10 +33,18 @@ class StudyPlanWidgetDelegate( private val studyPlanAdapter = DefaultDelegateAdapter().apply { addDelegate(StudyPlanSectionAdapterDelegate(onNewMessage)) - addDelegate(StudyPlanActivityAdapterDelegate(onNewMessage)) + addDelegate( + StudyPlanActivityAdapterDelegate { activityId -> + onNewMessage(StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) + } + ) addDelegate(sectionsLoadingAdapterDelegate()) - addDelegate(activitiesLoadingAdapterDelegate()) - addDelegate(activitiesErrorAdapterDelegate(onNewMessage)) + addDelegate(ActivityLoadingAdapterDelegate()) + addDelegate( + DataLoadingErrorAdapterDelegate { item -> + onNewMessage(StudyPlanWidgetFeature.Message.RetryActivitiesLoading(item.sectionId)) + } + ) } @ColorInt private val inactiveSectionTextColor: Int = @@ -45,10 +54,10 @@ class StudyPlanWidgetDelegate( ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface) @ColorInt private val activeActivityTextColor: Int = - ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface_alpha_87) + ContextCompat.getColor(context, StudyPlanRecyclerItem.Activity.activeTextColorRes) @ColorInt private val inactiveActivityTextColor: Int = - ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface_alpha_60) + ContextCompat.getColor(context, StudyPlanRecyclerItem.Activity.inactiveTextColorRes) private val sectionTopMargin = context.resources.getDimensionPixelOffset(R.dimen.study_plan_section_top_margin) @@ -56,13 +65,13 @@ class StudyPlanWidgetDelegate( context.resources.getDimensionPixelOffset(R.dimen.study_plan_activity_top_margin) private val lockIcon = - ContextCompat.getDrawable(context, R.drawable.ic_activity_locked) + ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.lockedActivityIconRes) private val activeIcon = - ContextCompat.getDrawable(context, R.drawable.ic_home_screen_arrow_button) + ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.nextActivityIconRes) private val skippedIcon = - ContextCompat.getDrawable(context, R.drawable.ic_topic_skipped) + ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.skippedActivityIconRes) private val completedIcon = - ContextCompat.getDrawable(context, R.drawable.ic_topic_completed) + ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.completedActivityIconRes) private var studyPlanViewStateDelegate: ViewStateDelegate? = null @@ -120,6 +129,7 @@ class StudyPlanWidgetDelegate( is StudyPlanRecyclerItem.ActivityLoading, is StudyPlanRecyclerItem.Activity, is StudyPlanRecyclerItem.ActivitiesError -> activityTopMargin + else -> 0 } fun cleanup() { @@ -146,25 +156,6 @@ class StudyPlanWidgetDelegate( R.layout.item_study_plan_section_loading ) - private fun activitiesLoadingAdapterDelegate() = - adapterDelegate( - R.layout.item_study_plan_activities_loading - ) - - private fun activitiesErrorAdapterDelegate( - onNewMessage: (StudyPlanWidgetFeature.Message) -> Unit - ) = - adapterDelegate( - R.layout.widget_data_loading_error - ) { - val viewBinding = WidgetDataLoadingErrorBinding.bind(itemView) - viewBinding.reloadButton.setOnClickListener { - item?.sectionId?.let { sectionId -> - onNewMessage(StudyPlanWidgetFeature.Message.RetryActivitiesLoading(sectionId)) - } - } - } - private fun mapContentToRecyclerItems( studyPlanContent: StudyPlanWidgetViewState.Content ): List = diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt index 3c4094a59b..061e1f2554 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt @@ -15,21 +15,14 @@ import org.hyperskill.app.android.gamification_toolbar.view.ui.delegate.Gamifica import org.hyperskill.app.android.home.view.ui.screen.HomeScreen import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter import org.hyperskill.app.android.problems_limit.view.ui.delegate.ProblemsLimitDelegate -import org.hyperskill.app.android.projects_selection.list.navigation.ProjectSelectionListScreen import org.hyperskill.app.android.stage_implementation.view.dialog.UnsupportedStageBottomSheet -import org.hyperskill.app.android.stage_implementation.view.navigation.StageImplementationScreen -import org.hyperskill.app.android.step.view.screen.StepScreen +import org.hyperskill.app.android.study_plan.delegate.LearningActivityTargetViewActionHandler import org.hyperskill.app.android.study_plan.delegate.StudyPlanWidgetDelegate -import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen import org.hyperskill.app.core.injection.ReduxViewModelFactory -import org.hyperskill.app.learning_activities.presentation.model.LearningActivityTargetViewAction -import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListParams import org.hyperskill.app.study_plan.presentation.StudyPlanScreenViewModel import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.view.model.StudyPlanWidgetViewState -import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams -import ru.nobird.android.view.base.ui.extension.showIfNotExists import ru.nobird.android.view.redux.ui.extension.reduxViewModel import ru.nobird.app.presentation.redux.container.ReduxView @@ -164,45 +157,15 @@ class StudyPlanFragment : } is StudyPlanScreenFeature.Action.ViewAction.ProblemsLimitViewAction -> {} is StudyPlanScreenFeature.Action.ViewAction.StudyPlanWidgetViewAction -> { - when (val viewAction = action.viewAction) { + when (val studyPlanWidgetViewAction = action.viewAction) { is StudyPlanWidgetFeature.Action.ViewAction.NavigateTo.Home -> { mainScreenRouter.switch(HomeScreen) } is StudyPlanWidgetFeature.Action.ViewAction.NavigateTo.LearningActivityTarget -> { - when (val activityViewAction = viewAction.viewAction) { - LearningActivityTargetViewAction.ShowStageImplementIDERequiredModal -> { - UnsupportedStageBottomSheet.newInstance() - .showIfNotExists(childFragmentManager, UnsupportedStageBottomSheet.TAG) - } - is LearningActivityTargetViewAction.NavigateTo.StageImplement -> { - requireRouter().navigateTo( - StageImplementationScreen( - projectId = activityViewAction.projectId, - stageId = activityViewAction.stageId - ) - ) - } - is LearningActivityTargetViewAction.NavigateTo.Step -> { - requireRouter().navigateTo(StepScreen(activityViewAction.stepRoute)) - } - is LearningActivityTargetViewAction.NavigateTo.SelectProject -> { - requireRouter().navigateTo( - ProjectSelectionListScreen( - ProjectSelectionListParams( - trackId = activityViewAction.trackId, - isNewUserMode = false - ) - ) - ) - } - LearningActivityTargetViewAction.NavigateTo.SelectTrack -> { - requireRouter().navigateTo( - TrackSelectionListScreen( - TrackSelectionListParams(isNewUserMode = false) - ) - ) - } - } + LearningActivityTargetViewActionHandler.handle( + fragment = this, + viewAction = studyPlanWidgetViewAction.viewAction + ) } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt index c5c20fbf7b..68d3d49de9 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt @@ -2,9 +2,10 @@ package org.hyperskill.app.android.study_plan.model import android.graphics.drawable.Drawable import androidx.annotation.ColorInt +import org.hyperskill.app.android.R import ru.nobird.app.core.model.Identifiable -sealed interface StudyPlanRecyclerItem { +interface StudyPlanRecyclerItem { data class Section( override val id: Long, val title: String, @@ -41,5 +42,15 @@ sealed interface StudyPlanRecyclerItem { val endIcon: Drawable?, val isClickable: Boolean, val isIdeRequired: Boolean - ) : StudyPlanRecyclerItem, Identifiable + ) : StudyPlanRecyclerItem, Identifiable { + companion object { + val activeTextColorRes: Int = org.hyperskill.app.R.color.color_on_surface_alpha_87 + val inactiveTextColorRes: Int = org.hyperskill.app.R.color.color_on_surface_alpha_60 + + const val nextActivityIconRes: Int = R.drawable.ic_home_screen_arrow_button + const val lockedActivityIconRes: Int = R.drawable.ic_activity_locked + const val skippedActivityIconRes: Int = R.drawable.ic_topic_skipped + const val completedActivityIconRes: Int = R.drawable.ic_topic_completed + } + } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/fragment_home.xml b/androidHyperskillApp/src/main/res/layout/fragment_home.xml index dfb23a2695..a95458a644 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_home.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_home.xml @@ -73,10 +73,10 @@ /> diff --git a/androidHyperskillApp/src/main/res/layout/layout_home_topics_to_discover_next.xml b/androidHyperskillApp/src/main/res/layout/layout_home_topics_to_discover_next.xml deleted file mode 100644 index 2abdef3bfd..0000000000 --- a/androidHyperskillApp/src/main/res/layout/layout_home_topics_to_discover_next.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_next_learning_activity.xml b/androidHyperskillApp/src/main/res/layout/layout_next_learning_activity.xml new file mode 100644 index 0000000000..cad8a02bea --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/layout_next_learning_activity.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NextLearningActivity/Views/NextLearningActivityView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NextLearningActivity/Views/NextLearningActivityView.swift index e1bf6551fb..675b9c9382 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NextLearningActivity/Views/NextLearningActivityView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NextLearningActivity/Views/NextLearningActivityView.swift @@ -57,6 +57,7 @@ struct NextLearningActivityView_Previews: PreviewProvider { NextLearningActivityView( stateKs: .content( NextLearningActivityWidgetFeatureViewStateContent( + id: nextPlaceholder.id, title: nextPlaceholder.title, subtitle: nextPlaceholder.subtitle, isIdeRequired: nextPlaceholder.isIdeRequired, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/presentation/NextLearningActivityWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/presentation/NextLearningActivityWidgetFeature.kt index 70f6e0ec96..8e1dd0fac4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/presentation/NextLearningActivityWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/presentation/NextLearningActivityWidgetFeature.kt @@ -34,6 +34,7 @@ object NextLearningActivityWidgetFeature { object NetworkError : ViewState object Empty : ViewState data class Content( + val id: Long, val title: String, val subtitle: String?, val isIdeRequired: Boolean, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/view/mapper/NextLearningActivityWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/view/mapper/NextLearningActivityWidgetViewStateMapper.kt index 4afe2fac81..30c1414d36 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/view/mapper/NextLearningActivityWidgetViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/next_learning_activity_widget/view/mapper/NextLearningActivityWidgetViewStateMapper.kt @@ -20,6 +20,7 @@ object NextLearningActivityWidgetViewStateMapper { } is NextLearningActivityWidgetFeature.ContentState.Content -> { NextLearningActivityWidgetFeature.ViewState.Content( + id = state.learningActivity.id, title = LearningActivityTextsMapper.mapLearningActivityToTitle(state.learningActivity), subtitle = LearningActivityTextsMapper.mapLearningActivityToSubtitle(state.learningActivity), isIdeRequired = state.learningActivity.isIdeRequired,