From 702ad41e70d26810d8e45fe2758aa273d7c8d91a Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 30 Jul 2024 17:09:13 +0200 Subject: [PATCH] Shared, Android skip suggestion (#1126) --- .../extensions/NestedScollViewExtention.kt | 7 + .../fragment/StageStepWrapperFragment.kt | 7 + .../step/view/delegate/StepMenuDelegate.kt | 12 +- .../step/view/fragment/StepWrapperFragment.kt | 19 +- .../step/view/model/StepMenuPrimaryAction.kt | 3 +- .../step/view/model/StepToolbarCallback.kt | 2 + .../step_practice/model/StepPracticeHost.kt | 3 + .../view/fragment/StepPracticeFragment.kt | 7 + .../StepQuizFeedbackBlocksDelegate.kt | 64 +++- .../view/fragment/DefaultStepQuizFragment.kt | 45 +-- .../view/mapper/StepQuizFeedbackMapper.kt | 55 ---- .../view/model/StepQuizFeedbackState.kt | 13 - .../drawable/bg_step_quiz_feedback_wrong.xml | 7 + .../drawable/ic_step_quiz_feedback_wrong.xml | 13 + .../layout_step_quiz_feedback_block.xml | 51 ++- .../src/main/res/menu/step_menu.xml | 14 +- .../src/main/res/values/styles.xml | 10 +- config/detekt/baseline.xml | 3 +- .../Views/Toolbar/StepToolbarContent.swift | 36 +-- .../Modules/StepQuiz/Views/StepQuizView.swift | 6 + .../app/step/presentation/StepViewModel.kt | 9 +- .../hyperskill/HyperskillAnalyticPart.kt | 3 +- ...enuActionClickedHyperskillAnalyticEvent.kt | 1 + .../hyperskill/app/step/domain/model/Step.kt | 9 +- .../domain/model/StepMenuSecondaryAction.kt | 1 + .../app/step/presentation/StepFeature.kt | 6 +- .../app/step/presentation/StepReducer.kt | 51 ++- .../step/view/mapper/StepViewStateMapper.kt | 13 +- ...dCommentsClickedHyperskillAnalyticEvent.kt | 31 ++ ...ckSeeHintClickedHyperskillAnalyticEvent.kt | 31 ++ ...dbackSkipClickedHyperskillAnalyticEvent.kt | 31 ++ .../step_quiz/injection/StepQuizComponent.kt | 2 + .../injection/StepQuizComponentImpl.kt | 14 +- .../step_quiz/presentation/StepQuizFeature.kt | 67 ++-- .../step_quiz/presentation/StepQuizReducer.kt | 68 +++- .../view/mapper/StepQuizFeedbackMapper.kt | 121 ++++++++ .../view/model/StepQuizFeedbackState.kt | 33 ++ .../presentation/StepQuizHintsFeature.kt | 7 + .../presentation/StepQuizHintsReducer.kt | 89 +++--- .../moko-resources/base/strings.xml | 8 + .../step/StepViewStateMapperTest.kt | 12 +- .../step_quiz/StepQuizFeedbackMapperTest.kt | 291 ++++++++++++++++++ .../org/hyperskill/step_quiz/StepQuizTest.kt | 206 ++++++++++++- 43 files changed, 1214 insertions(+), 267 deletions(-) delete mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/mapper/StepQuizFeedbackMapper.kt delete mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/model/StepQuizFeedbackState.kt create mode 100644 androidHyperskillApp/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml create mode 100644 androidHyperskillApp/src/main/res/drawable/ic_step_quiz_feedback_wrong.xml create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFeedbackReadCommentsClickedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFeedbackSeeHintClickedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFeedbackSkipClickedHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/NestedScollViewExtention.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/NestedScollViewExtention.kt index 1b8da2ab95..55f757a8d8 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/NestedScollViewExtention.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/NestedScollViewExtention.kt @@ -15,4 +15,11 @@ fun NestedScrollView.smoothScrollToBottom(durationMilliseconds: Int = DEFAULT_SM val y = view.bottom + lp.bottomMargin + scrollView.paddingBottom scrollView.smoothScrollTo(0, y, durationMilliseconds) } +} + +fun NestedScrollView.smoothScrollTo(view: View, durationMilliseconds: Int = DEFAULT_SMOOTH_SCROLL_DURATION) { + val scrollView = this + val lp = view.layoutParams as ViewGroup.MarginLayoutParams + val y = view.bottom + lp.bottomMargin + scrollView.smoothScrollTo(0, y, durationMilliseconds) } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt index 1606a542f2..7768b592db 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt @@ -12,6 +12,7 @@ import org.hyperskill.app.android.HyperskillApp import org.hyperskill.app.android.R import org.hyperskill.app.android.core.extensions.argument import org.hyperskill.app.android.core.extensions.logger +import org.hyperskill.app.android.core.extensions.smoothScrollTo import org.hyperskill.app.android.core.extensions.smoothScrollToBottom import org.hyperskill.app.android.core.view.ui.fragment.setChildFragment import org.hyperskill.app.android.core.view.ui.navigation.requireRouter @@ -228,4 +229,10 @@ class StageStepWrapperFragment : .stagePracticeContainer .smoothScrollToBottom(SMOOTH_SCROLL_DURATION_MILLISECONDS) } + + override fun scrollTo(view: View) { + viewBinding + .stagePracticeContainer + .smoothScrollTo(view, SMOOTH_SCROLL_DURATION_MILLISECONDS) + } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepMenuDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepMenuDelegate.kt index d22ddc4b20..a1599a1919 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepMenuDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepMenuDelegate.kt @@ -24,7 +24,7 @@ import org.hyperskill.app.step.domain.model.StepMenuSecondaryAction @OptIn(FlowPreview::class) class StepMenuDelegate( menuHost: MenuHost, - private val viewLifecycleOwner: LifecycleOwner, + viewLifecycleOwner: LifecycleOwner, private val onPrimaryActionClick: (StepMenuPrimaryAction) -> Unit, private val onSecondaryActionClick: (StepMenuSecondaryAction) -> Unit, private val onBackClick: () -> Unit @@ -89,7 +89,6 @@ class StepMenuDelegate( val menuItem: MenuItem? = menu.findItem( when (action) { StepMenuPrimaryAction.THEORY -> R.id.theory - StepMenuPrimaryAction.COMMENTS -> R.id.comments } ) val isVisible = params?.isVisible == true @@ -108,6 +107,7 @@ class StepMenuDelegate( StepMenuSecondaryAction.REPORT -> R.id.practiceFeedback StepMenuSecondaryAction.SKIP -> R.id.skip StepMenuSecondaryAction.OPEN_IN_WEB -> R.id.open_in_web + StepMenuSecondaryAction.COMMENTS -> R.id.comments } menu.findItem(menuItemId)?.isVisible = actions.contains(action) } @@ -115,14 +115,14 @@ class StepMenuDelegate( override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { - R.id.comments -> { - onPrimaryActionClick.invoke(StepMenuPrimaryAction.COMMENTS) - true - } R.id.theory -> { onPrimaryActionClick.invoke(StepMenuPrimaryAction.THEORY) true } + R.id.comments -> { + onSecondaryActionClick.invoke(StepMenuSecondaryAction.COMMENTS) + true + } R.id.practiceFeedback -> { onSecondaryActionClick.invoke(StepMenuSecondaryAction.REPORT) true diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt index e3f6807a45..04ec55f43d 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt @@ -22,7 +22,6 @@ import org.hyperskill.app.android.step.view.model.LimitsWidgetCallback import org.hyperskill.app.android.step.view.model.StepCompletionHost import org.hyperskill.app.android.step.view.model.StepCompletionView import org.hyperskill.app.android.step.view.model.StepMenuPrimaryAction -import org.hyperskill.app.android.step.view.model.StepMenuPrimaryActionParams import org.hyperskill.app.android.step.view.model.StepPracticeCallback import org.hyperskill.app.android.step.view.model.StepToolbarCallback import org.hyperskill.app.android.step.view.model.StepToolbarHost @@ -136,13 +135,6 @@ class StepWrapperFragment : initStepContainer(stepState) parentOfType(StepToolbarHost::class.java)?.apply { renderTopicProgress(state.stepToolbarViewState) - renderPrimaryAction( - StepMenuPrimaryAction.COMMENTS, - StepMenuPrimaryActionParams( - isVisible = state.isCommentsToolbarItemAvailable, - isEnabled = true - ) - ) renderSecondaryMenuActions(state.stepMenuSecondaryActions) } (childFragmentManager.findFragmentByTag(STEP_CONTENT_TAG) as? StepCompletionView) @@ -223,13 +215,18 @@ class StepWrapperFragment : (childFragmentManager.findFragmentByTag(STEP_CONTENT_TAG) as? StepPracticeCallback) ?.onTheoryClick() } - StepMenuPrimaryAction.COMMENTS -> { - stepViewModel.onCommentsClick() - } } } override fun onSecondaryActionClicked(action: StepMenuSecondaryAction) { stepViewModel.onActionClick(action) } + + override fun requestShowComments() { + stepViewModel.requestShowComments() + } + + override fun requestSkip() { + stepViewModel.requestSkip() + } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepMenuPrimaryAction.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepMenuPrimaryAction.kt index 6f848df6c9..a6673e89a7 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepMenuPrimaryAction.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepMenuPrimaryAction.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.android.step.view.model enum class StepMenuPrimaryAction { - THEORY, - COMMENTS + THEORY } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepToolbarCallback.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepToolbarCallback.kt index 9366748a8f..2dae115c0f 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepToolbarCallback.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/model/StepToolbarCallback.kt @@ -5,4 +5,6 @@ import org.hyperskill.app.step.domain.model.StepMenuSecondaryAction interface StepToolbarCallback { fun onPrimaryActionClicked(action: StepMenuPrimaryAction) fun onSecondaryActionClicked(action: StepMenuSecondaryAction) + fun requestShowComments() + fun requestSkip() } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/model/StepPracticeHost.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/model/StepPracticeHost.kt index 072d32c094..fa3b191d23 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/model/StepPracticeHost.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/model/StepPracticeHost.kt @@ -1,5 +1,8 @@ package org.hyperskill.app.android.step_practice.model +import android.view.View + interface StepPracticeHost { fun fullScrollDown() + fun scrollTo(view: View) } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/view/fragment/StepPracticeFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/view/fragment/StepPracticeFragment.kt index 5ac864bfe9..d34e0e64a0 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/view/fragment/StepPracticeFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_practice/view/fragment/StepPracticeFragment.kt @@ -7,6 +7,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import dev.chrisbanes.insetter.applyInsetter import org.hyperskill.app.android.R import org.hyperskill.app.android.core.extensions.argument +import org.hyperskill.app.android.core.extensions.smoothScrollTo import org.hyperskill.app.android.core.extensions.smoothScrollToBottom import org.hyperskill.app.android.core.view.ui.fragment.setChildFragment import org.hyperskill.app.android.databinding.FragmentStepPracticeBinding @@ -90,4 +91,10 @@ class StepPracticeFragment : .stepPracticeContainer .smoothScrollToBottom(SMOOTH_SCROLL_DURATION_MILLISECONDS) } + + override fun scrollTo(view: View) { + stepPracticeViewBinding + .stepPracticeContainer + .smoothScrollTo(view, SMOOTH_SCROLL_DURATION_MILLISECONDS) + } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt index 79e8a4ad13..fc5fd2e607 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt @@ -1,22 +1,27 @@ package org.hyperskill.app.android.step_quiz.view.delegate import android.content.Context +import android.graphics.Paint import android.graphics.drawable.AnimationDrawable import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import org.hyperskill.app.android.R import org.hyperskill.app.android.core.view.ui.widget.ProgressableWebViewClient import org.hyperskill.app.android.databinding.LayoutStepQuizFeedbackBlockBinding -import org.hyperskill.app.android.step_quiz.view.model.StepQuizFeedbackState +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature +import org.hyperskill.app.step_quiz.view.model.StepQuizFeedbackState +import org.hyperskill.app.step_quiz.view.model.StepQuizFeedbackState.Wrong.Action import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.getDrawableCompat class StepQuizFeedbackBlocksDelegate( context: Context, - private val layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding + private val layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding, + private val onNewMessage: (StepQuizFeature.Message) -> Unit ) { companion object { private const val EVALUATION_FRAME_DURATION_MS = 250 + private const val NON_LATEX_HINT_TEMPLATE = """
%s
""" } private val viewStateDelegate = ViewStateDelegate() @@ -24,7 +29,7 @@ class StepQuizFeedbackBlocksDelegate( init { with(viewStateDelegate) { addState() - addState( + addState( layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackUnsupported ) addState( @@ -38,7 +43,7 @@ class StepQuizFeedbackBlocksDelegate( layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackWrong, layoutStepQuizFeedbackBlockBinding.stepQuizFeedback ) - addState( + addState( layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation ) addState( @@ -58,6 +63,10 @@ class StepQuizFeedbackBlocksDelegate( textView?.typeface = ResourcesCompat.getFont(context, R.font.pt_mono) } + + with(layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackWrongAction) { + paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG + } } fun setState(state: StepQuizFeedbackState) { @@ -65,12 +74,39 @@ class StepQuizFeedbackBlocksDelegate( layoutStepQuizFeedbackBlockBinding.root.isVisible = state !is StepQuizFeedbackState.Idle when (state) { is StepQuizFeedbackState.Correct -> { - setHint(layoutStepQuizFeedbackBlockBinding, state.hint) + setHint(layoutStepQuizFeedbackBlockBinding, state.hint, state.useLatex) } is StepQuizFeedbackState.Wrong -> { - setHint(layoutStepQuizFeedbackBlockBinding, state.hint) + with(layoutStepQuizFeedbackBlockBinding) { + stepQuizFeedbackWrongTitle.text = state.title + stepQuizFeedbackWrongDescription.isVisible = state.description != null + if (state.description != null) { + stepQuizFeedbackWrongDescription.text = state.description + } + + stepQuizFeedbackWrongAction.isVisible = state.actionText != null + if (state.actionText != null) { + stepQuizFeedbackWrongAction.text = state.actionText + } + val action = state.actionType + stepQuizFeedbackWrongAction.setOnClickListener( + if (action != null) { + { + onNewMessage( + when (action) { + Action.SEE_HINT -> StepQuizFeature.Message.SeeHintClicked + Action.READ_COMMENTS -> StepQuizFeature.Message.ReadCommentsClicked + Action.SKIP_PROBLEM -> StepQuizFeature.Message.SkipClicked + } + ) + } + } else null + ) + + setHint(layoutStepQuizFeedbackBlockBinding, state.feedbackHint, state.useFeedbackHintLatex) + } } - is StepQuizFeedbackState.Validation -> { + is StepQuizFeedbackState.ValidationFailed -> { layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation.text = state.message } is StepQuizFeedbackState.RejectedSubmission -> { @@ -84,10 +120,18 @@ class StepQuizFeedbackBlocksDelegate( private fun setHint( layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding, - hint: String? + hint: String?, + useLatex: Boolean ) { - layoutStepQuizFeedbackBlockBinding.stepQuizFeedback.isVisible = hint != null - layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackBody.setText(hint) + val resultHint = hint?.let { + if (useLatex) { + hint + } else { + String.format(NON_LATEX_HINT_TEMPLATE, hint) + } + } + layoutStepQuizFeedbackBlockBinding.stepQuizFeedback.isVisible = resultHint != null + layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackBody.setText(resultHint) } private fun getEvaluationDrawable(context: Context): AnimationDrawable = diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 8a08d476df..d5d464ab63 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -33,6 +33,7 @@ import org.hyperskill.app.android.step.view.model.StepHost import org.hyperskill.app.android.step.view.model.StepMenuPrimaryAction import org.hyperskill.app.android.step.view.model.StepMenuPrimaryActionParams import org.hyperskill.app.android.step.view.model.StepPracticeCallback +import org.hyperskill.app.android.step.view.model.StepToolbarCallback import org.hyperskill.app.android.step.view.model.StepToolbarContentViewState import org.hyperskill.app.android.step.view.model.StepToolbarHost import org.hyperskill.app.android.step_practice.model.StepPracticeHost @@ -41,9 +42,7 @@ import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate import org.hyperskill.app.android.step_quiz.view.dialog.ProblemOnboardingBottomSheetCallback import org.hyperskill.app.android.step_quiz.view.dialog.ProblemsOnboardingBottomSheetFactory import org.hyperskill.app.android.step_quiz.view.factory.StepQuizViewStateDelegateFactory -import org.hyperskill.app.android.step_quiz.view.mapper.StepQuizFeedbackMapper import org.hyperskill.app.android.step_quiz.view.model.StepQuizButtonsState -import org.hyperskill.app.android.step_quiz.view.model.StepQuizFeedbackState import org.hyperskill.app.android.step_quiz_hints.delegate.StepQuizHintsDelegate import org.hyperskill.app.android.view.base.ui.extension.snackbar import org.hyperskill.app.problems_limit_info.domain.model.ProblemsLimitInfoModalFeatureParams @@ -51,7 +50,6 @@ import org.hyperskill.app.step.domain.model.BlockName import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_completion.presentation.StepCompletionFeature -import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizResolver import org.hyperskill.app.step_quiz.presentation.StepQuizViewModel @@ -91,9 +89,7 @@ abstract class DefaultStepQuizFragment : private var stepQuizStatsTextMapper: StepQuizStatsTextMapper? = null private var stepQuizTitleMapper: StepQuizTitleMapper? = null - private val stepQuizFeedbackMapper by lazy(LazyThreadSafetyMode.NONE) { - StepQuizFeedbackMapper() - } + private var stepQuizFeedbackMapper: org.hyperskill.app.step_quiz.view.mapper.StepQuizFeedbackMapper? = null protected abstract val quizViews: Array protected abstract val skeletonView: View @@ -118,6 +114,7 @@ abstract class DefaultStepQuizFragment : val platformStepQuizComponent = HyperskillApp.graph().buildPlatformStepQuizComponent(stepQuizComponent) stepQuizStatsTextMapper = stepQuizComponent.stepQuizStatsTextMapper stepQuizTitleMapper = stepQuizComponent.stepQuizTitleMapper + stepQuizFeedbackMapper = stepQuizComponent.stepQuizFeedbackMapper viewModelFactory = platformStepQuizComponent.reduxViewModelFactory } @@ -137,7 +134,11 @@ abstract class DefaultStepQuizFragment : ) viewBinding.stepQuizFeedbackBlocks.stepQuizFeedbackWrong.isHapticFeedbackEnabled = true stepQuizFeedbackBlocksDelegate = - StepQuizFeedbackBlocksDelegate(requireContext(), viewBinding.stepQuizFeedbackBlocks) + StepQuizFeedbackBlocksDelegate( + context = requireContext(), + layoutStepQuizFeedbackBlockBinding = viewBinding.stepQuizFeedbackBlocks, + onNewMessage = stepQuizViewModel::onNewMessage + ) stepQuizFormDelegate = createStepQuizFormDelegate().also { delegate -> delegate.customizeSubmissionButton(viewBinding.stepQuizButtons.stepQuizSubmitButton) } @@ -320,6 +321,18 @@ abstract class DefaultStepQuizFragment : StepQuizFeature.Action.ViewAction.ScrollToCallToActionButton -> { handleScrollToCallToActionButton() } + StepQuizFeature.Action.ViewAction.ScrollToHints -> { + parentOfType(StepPracticeHost::class.java) + ?.scrollTo(viewBinding.stepQuizHints.root) + } + is StepQuizFeature.Action.ViewAction.RequestShowComments -> { + parentOfType(StepToolbarCallback::class.java) + ?.requestShowComments() + } + is StepQuizFeature.Action.ViewAction.RequestSkipStep -> { + parentOfType(StepToolbarCallback::class.java) + ?.requestSkip() + } is StepQuizFeature.Action.ViewAction.StepQuizCodeBlanksViewAction -> { // no op } @@ -379,10 +392,12 @@ abstract class DefaultStepQuizFragment : } } + val feedbackState = stepQuizFeedbackMapper?.map(state) + if (feedbackState != null) { + stepQuizFeedbackBlocksDelegate?.setState(feedbackState) + } + when (val stepQuizState = state.stepQuizState) { - StepQuizFeature.StepQuizState.Unsupported -> { - stepQuizFeedbackBlocksDelegate?.setState(StepQuizFeedbackState.Unsupported) - } is StepQuizFeature.StepQuizState.AttemptLoaded -> { renderAttemptLoaded(stepQuizState) } @@ -412,21 +427,11 @@ abstract class DefaultStepQuizFragment : isCheckbox = state.attempt.dataset?.isCheckbox ) stepQuizFormDelegate?.setState(state) - stepQuizFeedbackBlocksDelegate?.setState( - stepQuizFeedbackMapper.mapToStepQuizFeedbackState(step.block.name, state) - ) viewBinding.stepQuizButtons.stepQuizSubmitButton.isEnabled = StepQuizResolver.isQuizEnabled(state) when (val submissionState = state.submissionState) { is StepQuizFeature.SubmissionState.Loaded -> { switchStepQuizButtonsState(getLoadedSubmissionButtonsState(submissionState.submission.status, step)) - - val replyValidation = submissionState.replyValidation - if (replyValidation is ReplyValidationResult.Error) { - stepQuizFeedbackBlocksDelegate?.setState( - StepQuizFeedbackState.Validation(replyValidation.message) - ) - } } is StepQuizFeature.SubmissionState.Empty -> { switchStepQuizButtonsState(StepQuizButtonsState.Submit) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/mapper/StepQuizFeedbackMapper.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/mapper/StepQuizFeedbackMapper.kt deleted file mode 100644 index 1b8495830c..0000000000 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/mapper/StepQuizFeedbackMapper.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.hyperskill.app.android.step_quiz.view.mapper - -import org.hyperskill.app.android.step_quiz.view.model.StepQuizFeedbackState -import org.hyperskill.app.step_quiz.presentation.StepQuizFeature -import org.hyperskill.app.submissions.domain.model.Submission -import org.hyperskill.app.submissions.domain.model.SubmissionStatus -import org.hyperskill.app.submissions.domain.model.formattedText - -class StepQuizFeedbackMapper { - fun mapToStepQuizFeedbackState( - stepBlockName: String?, - state: StepQuizFeature.StepQuizState - ): StepQuizFeedbackState { - val submissionState = (state as? StepQuizFeature.StepQuizState.AttemptLoaded)?.submissionState - return if (submissionState is StepQuizFeature.SubmissionState.Loaded) { - when (submissionState.submission.status) { - SubmissionStatus.CORRECT -> - StepQuizFeedbackState.Correct(formatHint(stepBlockName, submissionState.submission)) - - SubmissionStatus.WRONG -> - StepQuizFeedbackState.Wrong(formatHint(stepBlockName, submissionState.submission)) - - SubmissionStatus.EVALUATION -> - StepQuizFeedbackState.Evaluation - - SubmissionStatus.REJECTED -> { - val feedback = submissionState.submission.feedback - if (feedback != null) { - StepQuizFeedbackState.RejectedSubmission(feedback.formattedText()) - } else { - StepQuizFeedbackState.Idle - } - } - - else -> StepQuizFeedbackState.Idle - } - } else { - StepQuizFeedbackState.Idle - } - } - - private fun formatHint(stepBlockName: String?, submission: Submission): String? = - submission - .hint - ?.takeIf(String::isNotEmpty) - ?.replace("\n", "
") - ?.let { - val showLaTeX = stepBlockName == "math" - if (showLaTeX) { - it - } else { - """
$it
""" - } - } -} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/model/StepQuizFeedbackState.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/model/StepQuizFeedbackState.kt deleted file mode 100644 index 524e000441..0000000000 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/model/StepQuizFeedbackState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.hyperskill.app.android.step_quiz.view.model - -sealed class StepQuizFeedbackState { - object Idle : StepQuizFeedbackState() - data class Correct(val hint: String?) : StepQuizFeedbackState() - data class Wrong(val hint: String?) : StepQuizFeedbackState() - object Evaluation : StepQuizFeedbackState() - data class Validation(val message: String) : StepQuizFeedbackState() - - object Unsupported : StepQuizFeedbackState() - - data class RejectedSubmission(val message: String) : StepQuizFeedbackState() -} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml new file mode 100644 index 0000000000..4a2e7e115c --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable/ic_step_quiz_feedback_wrong.xml b/androidHyperskillApp/src/main/res/drawable/ic_step_quiz_feedback_wrong.xml new file mode 100644 index 0000000000..fafc24005c --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/ic_step_quiz_feedback_wrong.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml index b6a5286a62..6b52c206d5 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml @@ -6,29 +6,64 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + style="@style/StepQuizFeedback.Wrong" + android:orientation="vertical" + android:maxWidth="@dimen/auth_button_max_width"> + + + + + + + + diff --git a/androidHyperskillApp/src/main/res/menu/step_menu.xml b/androidHyperskillApp/src/main/res/menu/step_menu.xml index 595dc4ad00..6cb8ee0e7b 100644 --- a/androidHyperskillApp/src/main/res/menu/step_menu.xml +++ b/androidHyperskillApp/src/main/res/menu/step_menu.xml @@ -3,19 +3,19 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> - - + + match_parent wrap_content 48dp - 16dp - 16dp + 12dp + 12dp 12dp 12dp center_vertical @@ -94,6 +94,12 @@ + +