From b663098bf9287a4f483cdc53cf26a8211951616c Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 11:05:36 +0400 Subject: [PATCH 01/14] Setup base FillGaps ui --- androidHyperskillApp/build.gradle.kts | 1 + .../view/factory/StepQuizFragmentFactory.kt | 4 + .../FillBlanksStepQuizFormDelegate.kt | 103 ++++++++++++++++++ .../fragment/FillBlanksQuizFragment.kt | 51 +++++++++ .../bg_step_quiz_fill_blanks_input.xml | 9 ++ .../item_step_quiz_fill_blanks_input.xml | 21 ++++ .../item_step_quiz_fill_blanks_text.xml | 13 +++ .../layout_step_quiz_fill_blanks_binding.xml | 42 +++++++ .../src/main/res/values/dimens.xml | 6 + gradle/libs.versions.toml | 1 + .../presentation/FillBlanksItemMapper.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 4 + 12 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt create mode 100644 androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml create mode 100644 androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml create mode 100644 androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml create mode 100644 androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml diff --git a/androidHyperskillApp/build.gradle.kts b/androidHyperskillApp/build.gradle.kts index 0ceca04de4..1ec437f7f1 100644 --- a/androidHyperskillApp/build.gradle.kts +++ b/androidHyperskillApp/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(libs.android.ui.appcompat) implementation(libs.android.ui.constraintlayout) implementation(libs.android.ui.swiperefreshlayout) + implementation(libs.android.ui.flexbox) implementation(libs.android.ui.core.ktx) implementation(libs.android.ui.fragment) implementation(libs.android.ui.fragment.ktx) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt index 3f6ab457fe..189830921e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt @@ -3,6 +3,7 @@ package org.hyperskill.app.android.step_quiz.view.factory import androidx.fragment.app.Fragment import org.hyperskill.app.android.step_quiz_choice.view.fragment.ChoiceStepQuizFragment import org.hyperskill.app.android.step_quiz_code.view.fragment.CodeStepQuizFragment +import org.hyperskill.app.android.step_quiz_fill_blanks.fragment.FillBlanksQuizFragment import org.hyperskill.app.android.step_quiz_matching.view.fragment.MatchingStepQuizFragment import org.hyperskill.app.android.step_quiz_parsons.view.fragment.ParsonsStepQuizFragment import org.hyperskill.app.android.step_quiz_sorting.view.fragment.SortingStepQuizFragment @@ -42,6 +43,9 @@ object StepQuizFragmentFactory { BlockName.PARSONS -> ParsonsStepQuizFragment.newInstance(step, stepRoute) + BlockName.FILL_BLANKS -> + FillBlanksQuizFragment.newInstance(step, stepRoute) + else -> UnsupportedStepQuizFragment.newInstance() } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt new file mode 100644 index 0000000000..b4cc114aaf --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -0,0 +1,103 @@ +package org.hyperskill.app.android.step_quiz_fill_blanks.delegate + +import android.widget.TextView +import androidx.core.view.updateLayoutParams +import com.google.android.flexbox.FlexboxLayoutManager +import org.hyperskill.app.android.R +import org.hyperskill.app.android.databinding.LayoutStepQuizFillBlanksBindingBinding +import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate +import org.hyperskill.app.step_quiz.domain.model.submissions.Reply +import org.hyperskill.app.step_quiz.presentation.StepQuizFeature +import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksItem +import org.hyperskill.app.step_quiz_fill_blanks.model.InvalidFillBlanksConfigException +import org.hyperskill.app.step_quiz_fill_blanks.presentation.FillBlanksItemMapper +import org.hyperskill.app.step_quiz_fill_blanks.presentation.FillBlanksResolver +import ru.nobird.android.ui.adapterdelegates.dsl.adapterDelegate +import ru.nobird.android.ui.adapters.DefaultDelegateAdapter +import ru.nobird.android.view.base.ui.extension.setTextIfChanged + +class FillBlanksStepQuizFormDelegate( + private val binding: LayoutStepQuizFillBlanksBindingBinding +) : StepQuizFormDelegate { + + private val fillBlanksAdapter = DefaultDelegateAdapter().apply { + addDelegate(textAdapterDelegate()) + addDelegate( + inputAdapterDelegate { _, _ -> } + ) + } + + private var resolveState: ResolveState = ResolveState.NOT_RESOLVED + + init { + with(binding.stepQuizFillBlanksRecycler) { + itemAnimator = null + adapter = fillBlanksAdapter + isNestedScrollingEnabled = false + layoutManager = FlexboxLayoutManager(context) + } + } + + override fun setState(state: StepQuizFeature.StepQuizState.AttemptLoaded) { + resolve(state) + if (resolveState == ResolveState.RESOLVE_SUCCEED) { + val fillBlanksData = FillBlanksItemMapper.map( + state.attempt, + (state.submissionState as? StepQuizFeature.SubmissionState.Loaded)?.submission + ) + fillBlanksAdapter.items = fillBlanksData?.fillBlanks ?: emptyList() + binding.root.post { binding.stepQuizFillBlanksRecycler.requestLayout() } + } + } + + private fun resolve(state: StepQuizFeature.StepQuizState.AttemptLoaded) { + if (resolveState == ResolveState.NOT_RESOLVED) { + val dataset = state.attempt.dataset + resolveState = if (dataset != null) { + try { + FillBlanksResolver.resolve(dataset) + ResolveState.RESOLVE_SUCCEED + } catch (e: InvalidFillBlanksConfigException) { + ResolveState.RESOLVE_FAILED + } + } else { + ResolveState.RESOLVE_FAILED + } + } + } + + override fun createReply(): Reply = + Reply.fillBlanks( + blanks = fillBlanksAdapter.items.mapNotNull { item -> + when (item) { + is FillBlanksItem.Input -> item.inputText + is FillBlanksItem.Text -> null + } + } + ) + + private fun textAdapterDelegate() = + adapterDelegate(R.layout.item_step_quiz_fill_blanks_text) { + val textView = itemView as TextView + onBind { textItem -> + textView.updateLayoutParams { + isWrapBefore = textItem.startsWithNewLine + } + textView.setTextIfChanged(textItem.text) + } + } + + private fun inputAdapterDelegate(onClick: (Int, String) -> Unit) = + adapterDelegate(R.layout.item_step_quiz_fill_blanks_input) { + val textView = itemView as TextView + onBind { inputItem -> + textView.setTextIfChanged(inputItem.inputText ?: "") + } + } + + private enum class ResolveState { + NOT_RESOLVED, + RESOLVE_SUCCEED, + RESOLVE_FAILED + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt new file mode 100644 index 0000000000..0acad51beb --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt @@ -0,0 +1,51 @@ +package org.hyperskill.app.android.step_quiz_fill_blanks.fragment + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.hyperskill.app.android.databinding.LayoutStepQuizDescriptionBinding +import org.hyperskill.app.android.databinding.LayoutStepQuizFillBlanksBindingBinding +import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate +import org.hyperskill.app.android.step_quiz.view.fragment.DefaultStepQuizFragment +import org.hyperskill.app.android.step_quiz_fill_blanks.delegate.FillBlanksStepQuizFormDelegate +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step.domain.model.StepRoute + +class FillBlanksQuizFragment : DefaultStepQuizFragment() { + + companion object { + fun newInstance( + step: Step, + stepRoute: StepRoute + ): FillBlanksQuizFragment = + FillBlanksQuizFragment().apply { + this.step = step + this.stepRoute = stepRoute + } + } + + private var _binding: LayoutStepQuizFillBlanksBindingBinding? = null + private val binding: LayoutStepQuizFillBlanksBindingBinding + get() = requireNotNull(_binding) + + override val quizViews: Array + get() = arrayOf(binding.stepQuizFillBlanksContainer) + override val skeletonView: View + get() = binding.stepQuizFillBlanksSkeleton + + override val descriptionBinding: LayoutStepQuizDescriptionBinding? = null + + override fun createStepView(layoutInflater: LayoutInflater, parent: ViewGroup): View { + val binding = LayoutStepQuizFillBlanksBindingBinding.inflate(layoutInflater, parent, false) + this._binding = binding + return binding.root + } + + override fun createStepQuizFormDelegate(): StepQuizFormDelegate = + FillBlanksStepQuizFormDelegate(binding) + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml new file mode 100644 index 0000000000..aa24e7a4c7 --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml new file mode 100644 index 0000000000..c93689c787 --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml new file mode 100644 index 0000000000..9695573bee --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml new file mode 100644 index 0000000000..a1cccee1a1 --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index 0e1dadfa50..32819f358c 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -85,6 +85,12 @@ 8dp + 8dp + 32dp + 48dp + 180dp + 8dp + 48dp 45dp diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ca286d169..0e87470862 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,6 +63,7 @@ android-ui-material = { module = "com.google.android.material:material", version android-ui-appcompat = { module = "androidx.appcompat:appcompat", version = "1.4.0" } android-ui-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } android-ui-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" } +android-ui-flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" } android-ui-core-ktx = { module = "androidx.core:core-ktx", version = "1.3.0" } android-ui-fragment = { module = "androidx.fragment:fragment", version.ref = "fragment" } android-ui-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment" } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt index 0b433a485e..24c4a4b8c5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt @@ -14,11 +14,11 @@ object FillBlanksItemMapper { private val contentRegex: Regex = "
(.*?)
".toRegex(DotMatchesAllRegexOption) - fun map(attempt: Attempt, submission: Submission): FillBlanksData? = + fun map(attempt: Attempt, submission: Submission?): FillBlanksData? = attempt.dataset?.components?.let { mapInternal( componentsDataset = it, - replyBlanks = submission.reply?.blanks + replyBlanks = submission?.reply?.blanks ) } diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 9972eac97b..d7c58d7453 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -164,6 +164,10 @@ You\'ve unlocked a new problem type! Now you can rearrange the lines to make the code work. Use the "Tab" button to add indentation. + + + Fill in the gaps + Work on project. Stage %d/%d IDE required From 15179045c438112e9a82493bee7850b224bfd17a Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 12:41:00 +0400 Subject: [PATCH 02/14] Update margins and colors --- .../res/layout/item_step_quiz_fill_blanks_input.xml | 2 ++ .../res/layout/item_step_quiz_fill_blanks_text.xml | 1 + .../layout/layout_step_quiz_fill_blanks_binding.xml | 12 ++++++++++-- androidHyperskillApp/src/main/res/values/dimens.xml | 2 ++ .../presentation/FillBlanksItemMapper.kt | 2 +- shared/src/commonMain/resources/MR/colors/colors.xml | 6 ++++++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index c93689c787..bcc9576a79 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -18,4 +18,6 @@ android:background="@drawable/bg_step_quiz_fill_blanks_input" android:paddingLeft="@dimen/step_quiz_fill_blanks_padding" android:paddingRight="@dimen/step_quiz_fill_blanks_padding" + android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" + android:layout_marginHorizontal="@dimen/step_quiz_fill_blanks_input_item_horizontal_margin" /> \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml index 9695573bee..b53573882e 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml @@ -9,5 +9,6 @@ android:textSize="14sp" android:textColor="@color/color_on_surface_alpha_87" android:gravity="center_vertical" + android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" tools:text="This function should return a string!" /> \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml index a1cccee1a1..bee53b6e49 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml @@ -1,9 +1,15 @@ + android:layout_height="match_parent" + android:layout_marginTop="@dimen/step_quiz_content_padding_top" + android:layout_marginBottom="@dimen/step_quiz_content_padding_bottom" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/stepQuizHints" + app:layout_constraintBottom_toTopOf="@id/stepQuizStatistics"> diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index 32819f358c..e725fcad44 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -90,6 +90,8 @@ 48dp 180dp 8dp + 16dp + 8dp 48dp diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt index a70862e3e8..60bc7069b1 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt @@ -35,7 +35,7 @@ object FillBlanksItemMapper { ) } - private fun map( + internal fun map( componentsDataset: List, replyBlanks: List? ): FillBlanksData? { diff --git a/shared/src/commonMain/resources/MR/colors/colors.xml b/shared/src/commonMain/resources/MR/colors/colors.xml index 88dc567e16..e03331c1d4 100644 --- a/shared/src/commonMain/resources/MR/colors/colors.xml +++ b/shared/src/commonMain/resources/MR/colors/colors.xml @@ -23,6 +23,7 @@ #EDEDED #F7F9FA + #F7F9FA99 #EF341E #EF341E1F @@ -61,6 +62,7 @@ #121212 #1E1E1E #C4C4C4 + #12121299 #000000DE #00000099 #00000061 @@ -83,6 +85,10 @@ @color/color_gray_50 @color/color_black_850 + + @color/color_gray_50_alpha_60 + @color/color_black_850_alpha_60 + @color/color_black_900 @color/color_white_50 From 6bc815fcd04a5d3c63aca36a08bd01064a747c99 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 14:53:43 +0400 Subject: [PATCH 03/14] Implement input editing --- .../FillBlanksStepQuizFormDelegate.kt | 34 ++++++- .../dialog/FillBlanksInputDialogFragment.kt | 94 +++++++++++++++++++ .../fragment/FillBlanksQuizFragment.kt | 20 +++- ...> bg_step_quiz_fill_blanks_input_item.xml} | 0 ...step_quiz_fill_blanks_input_text_field.xml | 9 ++ .../res/layout/fragment_fill_blanks_input.xml | 31 ++++++ .../item_step_quiz_fill_blanks_input.xml | 2 +- .../src/main/res/values/dimens.xml | 2 + 8 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/dialog/FillBlanksInputDialogFragment.kt rename androidHyperskillApp/src/main/res/drawable/{bg_step_quiz_fill_blanks_input.xml => bg_step_quiz_fill_blanks_input_item.xml} (100%) create mode 100644 androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_text_field.xml create mode 100644 androidHyperskillApp/src/main/res/layout/fragment_fill_blanks_input.xml diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index b4cc114aaf..522a23608e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -2,10 +2,13 @@ package org.hyperskill.app.android.step_quiz_fill_blanks.delegate import android.widget.TextView import androidx.core.view.updateLayoutParams +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.FlexboxLayoutManager import org.hyperskill.app.android.R import org.hyperskill.app.android.databinding.LayoutStepQuizFillBlanksBindingBinding import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate +import org.hyperskill.app.android.step_quiz_fill_blanks.dialog.FillBlanksInputDialogFragment import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksItem @@ -15,15 +18,19 @@ import org.hyperskill.app.step_quiz_fill_blanks.presentation.FillBlanksResolver import ru.nobird.android.ui.adapterdelegates.dsl.adapterDelegate import ru.nobird.android.ui.adapters.DefaultDelegateAdapter import ru.nobird.android.view.base.ui.extension.setTextIfChanged +import ru.nobird.android.view.base.ui.extension.showIfNotExists +import ru.nobird.app.core.model.mutate class FillBlanksStepQuizFormDelegate( - private val binding: LayoutStepQuizFillBlanksBindingBinding + private val binding: LayoutStepQuizFillBlanksBindingBinding, + private val fragmentManager: FragmentManager, + private val onQuizChanged: (Reply) -> Unit ) : StepQuizFormDelegate { private val fillBlanksAdapter = DefaultDelegateAdapter().apply { addDelegate(textAdapterDelegate()) addDelegate( - inputAdapterDelegate { _, _ -> } + inputAdapterDelegate(::onInputItemClick) ) } @@ -76,6 +83,15 @@ class FillBlanksStepQuizFormDelegate( } ) + fun onInputItemModified(index: Int, text: String) { + fillBlanksAdapter.items = fillBlanksAdapter.items.mutate { + val inputItem = get(index) as FillBlanksItem.Input + set(index, inputItem.copy(inputText = text)) + } + fillBlanksAdapter.notifyItemChanged(index) + onQuizChanged(createReply()) + } + private fun textAdapterDelegate() = adapterDelegate(R.layout.item_step_quiz_fill_blanks_text) { val textView = itemView as TextView @@ -87,14 +103,26 @@ class FillBlanksStepQuizFormDelegate( } } - private fun inputAdapterDelegate(onClick: (Int, String) -> Unit) = + private fun inputAdapterDelegate(onClick: (index: Int, String) -> Unit) = adapterDelegate(R.layout.item_step_quiz_fill_blanks_input) { val textView = itemView as TextView + textView.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + onClick(position, textView.text.toString()) + } + } onBind { inputItem -> textView.setTextIfChanged(inputItem.inputText ?: "") } } + private fun onInputItemClick(index: Int, text: String) { + FillBlanksInputDialogFragment + .newInstance(index, text) + .showIfNotExists(fragmentManager, FillBlanksInputDialogFragment.TAG) + } + private enum class ResolveState { NOT_RESOLVED, RESOLVE_SUCCEED, diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/dialog/FillBlanksInputDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/dialog/FillBlanksInputDialogFragment.kt new file mode 100644 index 0000000000..939e4f019a --- /dev/null +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/dialog/FillBlanksInputDialogFragment.kt @@ -0,0 +1,94 @@ +package org.hyperskill.app.android.step_quiz_fill_blanks.dialog + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.hyperskill.app.android.R +import org.hyperskill.app.android.databinding.FragmentFillBlanksInputBinding +import ru.nobird.android.view.base.ui.extension.argument + +class FillBlanksInputDialogFragment : BottomSheetDialogFragment() { + companion object { + const val TAG: String = "FillBlanksInputDialogFragment" + + private const val ARG_INDEX = "INDEX" + private const val ARG_TEXT = "TEXT" + + fun newInstance( + index: Int, + text: String + ): FillBlanksInputDialogFragment = + FillBlanksInputDialogFragment().apply { + this.index = index + this.text = text + } + } + + private var index: Int by argument() + private var text: String by argument() + + private val fillBlanksInputBinding: FragmentFillBlanksInputBinding by viewBinding( + FragmentFillBlanksInputBinding::bind + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.TopCornersRoundedBottomSheetDialog) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + inflater.inflate(R.layout.fragment_fill_blanks_input, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (savedInstanceState != null) { + index = savedInstanceState.getInt(ARG_INDEX) + text = savedInstanceState.getString(ARG_TEXT) ?: return + } + with(fillBlanksInputBinding) { + fillBlanksInputField.append(text) + fillBlanksInputField.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + super.dismiss() + } + false + } + fillBlanksInputField.post { + fillBlanksInputField.requestFocus() + val inputMethodManager = + requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput(fillBlanksInputField, InputMethodManager.SHOW_IMPLICIT) + } + } + } + + override fun onPause() { + (parentFragment as? Callback) + ?.onSyncInputItemWithParent( + index = index, + text = fillBlanksInputBinding.fillBlanksInputField.text.toString() + ) + super.onPause() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(ARG_INDEX, index) + outState.putString(ARG_TEXT, text) + } + + interface Callback { + fun onSyncInputItemWithParent(index: Int, text: String) + } +} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt index 0acad51beb..39f7922854 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt @@ -8,10 +8,13 @@ import org.hyperskill.app.android.databinding.LayoutStepQuizFillBlanksBindingBin import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate import org.hyperskill.app.android.step_quiz.view.fragment.DefaultStepQuizFragment import org.hyperskill.app.android.step_quiz_fill_blanks.delegate.FillBlanksStepQuizFormDelegate +import org.hyperskill.app.android.step_quiz_fill_blanks.dialog.FillBlanksInputDialogFragment import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute -class FillBlanksQuizFragment : DefaultStepQuizFragment() { +class FillBlanksQuizFragment : + DefaultStepQuizFragment(), + FillBlanksInputDialogFragment.Callback { companion object { fun newInstance( @@ -28,6 +31,8 @@ class FillBlanksQuizFragment : DefaultStepQuizFragment() { private val binding: LayoutStepQuizFillBlanksBindingBinding get() = requireNotNull(_binding) + private var fillBlanksStepQuizFormDelegate: FillBlanksStepQuizFormDelegate? = null + override val quizViews: Array get() = arrayOf(binding.stepQuizFillBlanksContainer) override val skeletonView: View @@ -42,10 +47,21 @@ class FillBlanksQuizFragment : DefaultStepQuizFragment() { } override fun createStepQuizFormDelegate(): StepQuizFormDelegate = - FillBlanksStepQuizFormDelegate(binding) + FillBlanksStepQuizFormDelegate( + binding = binding, + fragmentManager = childFragmentManager, + onQuizChanged = ::syncReplyState + ).also { + this.fillBlanksStepQuizFormDelegate = it + } override fun onDestroyView() { super.onDestroyView() _binding = null + fillBlanksStepQuizFormDelegate = null + } + + override fun onSyncInputItemWithParent(index: Int, text: String) { + fillBlanksStepQuizFormDelegate?.onInputItemModified(index, text) } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml similarity index 100% rename from androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input.xml rename to androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_text_field.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_text_field.xml new file mode 100644 index 0000000000..f5768edba4 --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_text_field.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/fragment_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/fragment_fill_blanks_input.xml new file mode 100644 index 0000000000..b77591193d --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/fragment_fill_blanks_input.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index bcc9576a79..527d4cc66d 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -15,7 +15,7 @@ android:lines="1" android:ellipsize="end" android:gravity="center" - android:background="@drawable/bg_step_quiz_fill_blanks_input" + android:background="@drawable/bg_step_quiz_fill_blanks_input_item" android:paddingLeft="@dimen/step_quiz_fill_blanks_padding" android:paddingRight="@dimen/step_quiz_fill_blanks_padding" android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index e725fcad44..f06230eade 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -85,6 +85,7 @@ 8dp + 8dp 32dp 48dp @@ -92,6 +93,7 @@ 8dp 16dp 8dp + 56dp 48dp From 41befd377d910a9ff657c846540bf34d128ef967 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 15:22:57 +0400 Subject: [PATCH 04/14] Add ripple effect to the input item --- .../bg_step_quiz_fill_blanks_input_item.xml | 21 ++++++++++++------- .../item_step_quiz_fill_blanks_input.xml | 2 ++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml index aa24e7a4c7..9f3fb9c50e 100644 --- a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml +++ b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_input_item.xml @@ -1,9 +1,14 @@ - - - - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index 527d4cc66d..58936ff045 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -16,6 +16,8 @@ android:ellipsize="end" android:gravity="center" android:background="@drawable/bg_step_quiz_fill_blanks_input_item" + android:clickable="true" + android:focusable="true" android:paddingLeft="@dimen/step_quiz_fill_blanks_padding" android:paddingRight="@dimen/step_quiz_fill_blanks_padding" android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" From 846cb45f33b737e163f24a0fcbe632e315be2fe1 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 16:09:18 +0400 Subject: [PATCH 05/14] Update text appearance --- androidHyperskillApp/src/main/res/font/menlo.xml | 11 +++++++++++ .../res/layout/item_step_quiz_fill_blanks_input.xml | 6 +++--- .../res/layout/item_step_quiz_fill_blanks_text.xml | 5 +---- androidHyperskillApp/src/main/res/values/type.xml | 7 +++++++ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 androidHyperskillApp/src/main/res/font/menlo.xml diff --git a/androidHyperskillApp/src/main/res/font/menlo.xml b/androidHyperskillApp/src/main/res/font/menlo.xml new file mode 100644 index 0000000000..a577f87aaa --- /dev/null +++ b/androidHyperskillApp/src/main/res/font/menlo.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index 58936ff045..167a706df1 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -2,14 +2,13 @@ \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml index b53573882e..f8131e88d9 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml @@ -1,13 +1,10 @@ false + + \ No newline at end of file From 2c81fa65aae3c128e8a6052d2762fb2c2de5c9cd Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 18 Oct 2023 16:18:52 +0400 Subject: [PATCH 06/14] Cleanup --- .../delegate/FillBlanksStepQuizFormDelegate.kt | 15 ++++++++++----- .../layout/item_step_quiz_fill_blanks_input.xml | 1 - 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index 522a23608e..742d5e57b4 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -46,7 +46,8 @@ class FillBlanksStepQuizFormDelegate( } override fun setState(state: StepQuizFeature.StepQuizState.AttemptLoaded) { - resolve(state) + val resolveState = resolve(resolveState, state) + this.resolveState = resolveState if (resolveState == ResolveState.RESOLVE_SUCCEED) { val fillBlanksData = FillBlanksItemMapper.map( state.attempt, @@ -57,10 +58,13 @@ class FillBlanksStepQuizFormDelegate( } } - private fun resolve(state: StepQuizFeature.StepQuizState.AttemptLoaded) { - if (resolveState == ResolveState.NOT_RESOLVED) { + private fun resolve( + currentResolveState: ResolveState, + state: StepQuizFeature.StepQuizState.AttemptLoaded + ): ResolveState = + if (currentResolveState == ResolveState.NOT_RESOLVED) { val dataset = state.attempt.dataset - resolveState = if (dataset != null) { + if (dataset != null) { try { FillBlanksResolver.resolve(dataset) ResolveState.RESOLVE_SUCCEED @@ -70,8 +74,9 @@ class FillBlanksStepQuizFormDelegate( } else { ResolveState.RESOLVE_FAILED } + } else { + currentResolveState } - } override fun createReply(): Reply = Reply.fillBlanks( diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index 167a706df1..6e88892eb6 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -1,7 +1,6 @@ Date: Wed, 18 Oct 2023 16:24:47 +0400 Subject: [PATCH 07/14] Parse text items as html --- .../delegate/FillBlanksStepQuizFormDelegate.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index 742d5e57b4..2f8beda4ee 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -1,6 +1,8 @@ package org.hyperskill.app.android.step_quiz_fill_blanks.delegate +import android.util.Log import android.widget.TextView +import androidx.core.text.HtmlCompat import androidx.core.view.updateLayoutParams import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView @@ -101,10 +103,13 @@ class FillBlanksStepQuizFormDelegate( adapterDelegate(R.layout.item_step_quiz_fill_blanks_text) { val textView = itemView as TextView onBind { textItem -> + Log.d("FillBlanksStepQuizFormDelegate", "textAdapterDelegate.onBing") textView.updateLayoutParams { isWrapBefore = textItem.startsWithNewLine } - textView.setTextIfChanged(textItem.text) + textView.setTextIfChanged( + HtmlCompat.fromHtml(textItem.text, HtmlCompat.FROM_HTML_MODE_COMPACT) + ) } } From 721417181abec3bc35767ff1d4653809fdf70060 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 19 Oct 2023 13:23:11 +0400 Subject: [PATCH 08/14] Fix spacing --- .../delegate/FillBlanksStepQuizFormDelegate.kt | 2 -- .../src/main/res/layout/item_step_quiz_fill_blanks_input.xml | 1 - 2 files changed, 3 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index 2f8beda4ee..c9bde0aee3 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.android.step_quiz_fill_blanks.delegate -import android.util.Log import android.widget.TextView import androidx.core.text.HtmlCompat import androidx.core.view.updateLayoutParams @@ -103,7 +102,6 @@ class FillBlanksStepQuizFormDelegate( adapterDelegate(R.layout.item_step_quiz_fill_blanks_text) { val textView = itemView as TextView onBind { textItem -> - Log.d("FillBlanksStepQuizFormDelegate", "textAdapterDelegate.onBing") textView.updateLayoutParams { isWrapBefore = textItem.startsWithNewLine } diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index 6e88892eb6..4744f0f0ef 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -19,6 +19,5 @@ android:paddingLeft="@dimen/step_quiz_fill_blanks_padding" android:paddingRight="@dimen/step_quiz_fill_blanks_padding" android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" - android:layout_marginHorizontal="@dimen/step_quiz_fill_blanks_input_item_horizontal_margin" tools:text="List[Int]" /> \ No newline at end of file From aeb4ea8a8e8229ae79025f7e9ac13a26b5560a01 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 19 Oct 2023 14:21:04 +0400 Subject: [PATCH 09/14] Update fillBlanks skeleton --- .../fragment/FillBlanksQuizFragment.kt | 2 +- .../layout/layout_fill_blanks_skeleton.xml | 123 ++++++++++++++++++ .../layout_step_quiz_fill_blanks_binding.xml | 7 +- .../src/main/res/values/dimens.xml | 2 + 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt index 39f7922854..841fd1dd5b 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt @@ -36,7 +36,7 @@ class FillBlanksQuizFragment : override val quizViews: Array get() = arrayOf(binding.stepQuizFillBlanksContainer) override val skeletonView: View - get() = binding.stepQuizFillBlanksSkeleton + get() = binding.stepQuizFillBlanksSkeleton.root override val descriptionBinding: LayoutStepQuizDescriptionBinding? = null diff --git a/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml b/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml new file mode 100644 index 0000000000..39c1ee422c --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml index bee53b6e49..94602061e9 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml @@ -35,16 +35,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_background_alpha_60" - android:padding="20dp" + android:padding="@dimen/step_quiz_fill_blanks_content_padding" /> - + layout="@layout/layout_fill_blanks_skeleton"/> \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index f06230eade..20cd0b0a7c 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -94,6 +94,8 @@ 16dp 8dp 56dp + 14sp + 20dp 48dp From a9c2adba023772df82b34e608df809af04fdeb14 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Thu, 19 Oct 2023 14:24:07 +0400 Subject: [PATCH 10/14] Fix skeleton background --- .../src/main/res/layout/layout_fill_blanks_skeleton.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml b/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml index 39c1ee422c..4105e8f62a 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_fill_blanks_skeleton.xml @@ -24,7 +24,8 @@ android:layout_height="wrap_content" app:flexWrap="wrap" app:alignItems="center" - android:padding="@dimen/step_quiz_fill_blanks_content_padding"> + android:padding="@dimen/step_quiz_fill_blanks_content_padding" + android:background="@color/color_background_alpha_60"> Date: Fri, 20 Oct 2023 10:24:50 +0400 Subject: [PATCH 11/14] Fix vertical item spacing --- .../delegate/FillBlanksStepQuizFormDelegate.kt | 10 ++++++++++ .../bg_step_quiz_fill_blanks_item_vertical_divider.xml | 6 ++++++ .../res/layout/item_step_quiz_fill_blanks_input.xml | 1 - .../res/layout/item_step_quiz_fill_blanks_text.xml | 1 - 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_item_vertical_divider.xml diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index c9bde0aee3..daec505833 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -1,10 +1,12 @@ package org.hyperskill.app.android.step_quiz_fill_blanks.delegate import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.core.view.updateLayoutParams import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.flexbox.FlexboxItemDecoration import com.google.android.flexbox.FlexboxLayoutManager import org.hyperskill.app.android.R import org.hyperskill.app.android.databinding.LayoutStepQuizFillBlanksBindingBinding @@ -43,6 +45,14 @@ class FillBlanksStepQuizFormDelegate( adapter = fillBlanksAdapter isNestedScrollingEnabled = false layoutManager = FlexboxLayoutManager(context) + addItemDecoration( + FlexboxItemDecoration(context).apply { + setOrientation(FlexboxItemDecoration.HORIZONTAL) + setDrawable( + ContextCompat.getDrawable(context, R.drawable.bg_step_quiz_fill_blanks_item_vertical_divider) + ) + } + ) } } diff --git a/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_item_vertical_divider.xml b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_item_vertical_divider.xml new file mode 100644 index 0000000000..cc488685a6 --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/bg_step_quiz_fill_blanks_item_vertical_divider.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml index 4744f0f0ef..9e7be03992 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_input.xml @@ -18,6 +18,5 @@ android:focusable="true" android:paddingLeft="@dimen/step_quiz_fill_blanks_padding" android:paddingRight="@dimen/step_quiz_fill_blanks_padding" - android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" tools:text="List[Int]" /> \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml index f8131e88d9..8026701a4d 100644 --- a/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml +++ b/androidHyperskillApp/src/main/res/layout/item_step_quiz_fill_blanks_text.xml @@ -6,6 +6,5 @@ android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.AppTheme.Code.Medium" android:gravity="center_vertical" - android:layout_marginTop="@dimen/step_quiz_fill_blanks_item_top_margin" tools:text="This function should return a string!" /> \ No newline at end of file From 86b2d45375a40268b1177bf32fd6b2e0dd7a3f1c Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Fri, 20 Oct 2023 10:34:22 +0400 Subject: [PATCH 12/14] Cleanup --- .../step_quiz/view/factory/StepQuizFragmentFactory.kt | 4 ++-- ...lBlanksQuizFragment.kt => FillBlanksStepQuizFragment.kt} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/{FillBlanksQuizFragment.kt => FillBlanksStepQuizFragment.kt} (95%) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt index 189830921e..834c94323e 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/factory/StepQuizFragmentFactory.kt @@ -3,7 +3,7 @@ package org.hyperskill.app.android.step_quiz.view.factory import androidx.fragment.app.Fragment import org.hyperskill.app.android.step_quiz_choice.view.fragment.ChoiceStepQuizFragment import org.hyperskill.app.android.step_quiz_code.view.fragment.CodeStepQuizFragment -import org.hyperskill.app.android.step_quiz_fill_blanks.fragment.FillBlanksQuizFragment +import org.hyperskill.app.android.step_quiz_fill_blanks.fragment.FillBlanksStepQuizFragment import org.hyperskill.app.android.step_quiz_matching.view.fragment.MatchingStepQuizFragment import org.hyperskill.app.android.step_quiz_parsons.view.fragment.ParsonsStepQuizFragment import org.hyperskill.app.android.step_quiz_sorting.view.fragment.SortingStepQuizFragment @@ -44,7 +44,7 @@ object StepQuizFragmentFactory { ParsonsStepQuizFragment.newInstance(step, stepRoute) BlockName.FILL_BLANKS -> - FillBlanksQuizFragment.newInstance(step, stepRoute) + FillBlanksStepQuizFragment.newInstance(step, stepRoute) else -> UnsupportedStepQuizFragment.newInstance() diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksStepQuizFragment.kt similarity index 95% rename from androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt rename to androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksStepQuizFragment.kt index 841fd1dd5b..55a0bb3e54 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/fragment/FillBlanksStepQuizFragment.kt @@ -12,7 +12,7 @@ import org.hyperskill.app.android.step_quiz_fill_blanks.dialog.FillBlanksInputDi import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute -class FillBlanksQuizFragment : +class FillBlanksStepQuizFragment : DefaultStepQuizFragment(), FillBlanksInputDialogFragment.Callback { @@ -20,8 +20,8 @@ class FillBlanksQuizFragment : fun newInstance( step: Step, stepRoute: StepRoute - ): FillBlanksQuizFragment = - FillBlanksQuizFragment().apply { + ): FillBlanksStepQuizFragment = + FillBlanksStepQuizFragment().apply { this.step = step this.stepRoute = stepRoute } From 9181a7c2902d490559ccafa9cdd894614bddf16a Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Fri, 20 Oct 2023 11:30:46 +0400 Subject: [PATCH 13/14] Fix merge conflicts --- .../delegate/FillBlanksStepQuizFormDelegate.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt index daec505833..c22f5c9bd0 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fill_blanks/delegate/FillBlanksStepQuizFormDelegate.kt @@ -37,6 +37,8 @@ class FillBlanksStepQuizFormDelegate( ) } + private val fillBlanksMapper: FillBlanksItemMapper = FillBlanksItemMapper() + private var resolveState: ResolveState = ResolveState.NOT_RESOLVED init { @@ -60,7 +62,7 @@ class FillBlanksStepQuizFormDelegate( val resolveState = resolve(resolveState, state) this.resolveState = resolveState if (resolveState == ResolveState.RESOLVE_SUCCEED) { - val fillBlanksData = FillBlanksItemMapper.map( + val fillBlanksData = fillBlanksMapper.map( state.attempt, (state.submissionState as? StepQuizFeature.SubmissionState.Loaded)?.submission ) From 922ab41b2fc15acbe52a5a6cd4760bb7a564e7bb Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Fri, 20 Oct 2023 12:16:50 +0400 Subject: [PATCH 14/14] Add docs --- .../model/FillBlanksItem.kt | 15 +++++++++++++++ .../presentation/FillBlanksItemMapper.kt | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/model/FillBlanksItem.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/model/FillBlanksItem.kt index c4bfa3daf8..11c3331e29 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/model/FillBlanksItem.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/model/FillBlanksItem.kt @@ -3,12 +3,27 @@ package org.hyperskill.app.step_quiz_fill_blanks.model import ru.nobird.app.core.model.Identifiable sealed interface FillBlanksItem : Identifiable { + /** + * Represents a textual fill-in-the-blank item in a quiz. + * + * @param id The order number in a list of items. + * @param text The content of the fill-in-the-blank text. + * + * @param startsWithNewLine Indicates whether the text item should start with a new line preventive. + * If true, then the '\n' was placed before the text. + */ data class Text( override val id: Int, val text: String, val startsWithNewLine: Boolean ) : FillBlanksItem + /** + * Represents an input fill-in-the-blank item in a quiz. + * + * @param id The order number in a list of items. + * @param inputText The input text provided by the user. + */ data class Input( override val id: Int, val inputText: String? diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt index 70c9bf9934..055b08ff69 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_fill_blanks/presentation/FillBlanksItemMapper.kt @@ -9,9 +9,26 @@ import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksConfig import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksData import org.hyperskill.app.step_quiz_fill_blanks.model.FillBlanksItem +import org.hyperskill.app.step_quiz_fill_blanks.presentation.FillBlanksItemMapper.Companion.WHITE_SPACE_HTML_STRING import ru.nobird.app.core.model.mutate import ru.nobird.app.core.model.slice +/** + * Extracts content from
...
 HTML tags.
+ * Extracts the language name from the , if present.
+ *
+ * Maps content into a list of [FillBlanksItem],
+ * replacing the '▭' with [FillBlanksItem.Input], and the rest of the text with [FillBlanksItem.Text].
+ *
+ * Splits the text by '\n'.
+ * It is necessary to make text items smaller, and not occupy the whole width of the widget.
+ * This way, there will be enough space for input items,
+ * so that they will be placed next to the text item, not on the next line.
+ *
+ * Replace the leading whitespaces with the [WHITE_SPACE_HTML_STRING] so that HTML parser keeps them as is.
+ *
+ * Fill the [FillBlanksItem.Input] with the data from [Reply].blanks or [Component].text with the type == "input".
+ */
 class FillBlanksItemMapper {
     companion object {
         private const val LINE_BREAK_CHAR = '\n'