From 6b505d3b5490fae62a5223f4635e0edf1d899385 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 20 Jun 2019 13:26:00 +0300 Subject: [PATCH 001/108] check items colors and drawables --- .../main/res/drawable/ic_correct_checkmark.xml | 7 +++++++ .../res/drawable/ic_incorrect_exclamation.xml | 18 ++++++++++++++++++ app/src/main/res/values/colors.xml | 9 +++++++++ 3 files changed, 34 insertions(+) create mode 100644 app/src/main/res/drawable/ic_correct_checkmark.xml create mode 100644 app/src/main/res/drawable/ic_incorrect_exclamation.xml diff --git a/app/src/main/res/drawable/ic_correct_checkmark.xml b/app/src/main/res/drawable/ic_correct_checkmark.xml new file mode 100644 index 0000000000..71eda325eb --- /dev/null +++ b/app/src/main/res/drawable/ic_correct_checkmark.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/ic_incorrect_exclamation.xml b/app/src/main/res/drawable/ic_incorrect_exclamation.xml new file mode 100644 index 0000000000..efdd59eb81 --- /dev/null +++ b/app/src/main/res/drawable/ic_incorrect_exclamation.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e27779a562..83a582fc30 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -198,4 +198,13 @@ #2066CC66 @color/new_primary_color + + + #e8eafa + #e7f7e7 + #ffebe7 + + @color/color_violet_1 + #64cc64 + #ff7965 \ No newline at end of file From 0cf4dce1e53bf95d30fcd3851e767bc5370acb54 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 20 Jun 2019 14:57:30 +0300 Subject: [PATCH 002/108] string quiz fragment draft --- .../util/resolvers/StepTypeResolverImpl.java | 1 + .../ui/factory/StepQuizFragmentFactoryImpl.kt | 9 +++++- .../ui/fragment/StringStepQuizFragment.kt | 28 +++++++++++++++++++ .../res/layout/fragment_step_quiz_string.xml | 26 +++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt create mode 100644 app/src/main/res/layout/fragment_step_quiz_string.xml diff --git a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java index 97a196347b..ae1738d17b 100644 --- a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java +++ b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java @@ -172,6 +172,7 @@ public boolean isNeedUseOldStepContainer(@NotNull Step step) { switch (step.getBlock().getName()) { case AppConstants.TYPE_TEXT: case AppConstants.TYPE_VIDEO: + case AppConstants.TYPE_STRING: return false; default: return true; diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index 356eb57e94..39f43ad6f5 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -3,13 +3,20 @@ package org.stepik.android.view.step_quiz.ui.factory import android.support.v4.app.Fragment import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants +import org.stepik.android.view.step_quiz_string.ui.fragment.StringStepQuizFragment import javax.inject.Inject class StepQuizFragmentFactoryImpl @Inject constructor() : StepQuizFragmentFactory { override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment = - Fragment() + when (stepPersistentWrapper.step.block?.name) { + AppConstants.TYPE_STRING -> + StringStepQuizFragment.newInstance(stepPersistentWrapper) + + else -> + Fragment() + } override fun isStepCanHaveQuiz(stepPersistentWrapper: StepPersistentWrapper): Boolean = stepPersistentWrapper.step.block?.name != AppConstants.TYPE_VIDEO diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt new file mode 100644 index 0000000000..2c97438cae --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt @@ -0,0 +1,28 @@ +package org.stepik.android.view.step_quiz_string.ui.fragment + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.stepic.droid.R +import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.util.argument +import org.stepik.android.domain.lesson.model.LessonData + +class StringStepQuizFragment : Fragment() { + companion object { + fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = + StringStepQuizFragment() + .apply { +// this.lessonData = lessonData + this.stepWrapper = stepPersistentWrapper + } + } + + private var lessonData: LessonData by argument() + private var stepWrapper: StepPersistentWrapper by argument() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = + inflater.inflate(R.layout.fragment_step_quiz_string, container, false) +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml new file mode 100644 index 0000000000..bc6b6e8b47 --- /dev/null +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file From 493613b74125131998291ef0c367408c3b637225 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 20 Jun 2019 14:57:38 +0300 Subject: [PATCH 003/108] step submit button --- .../color/color_step_submit_button_text.xml | 5 +++++ .../bg_step_submit_button_ripple.xml | 11 +++++++++++ .../res/drawable/bg_step_submit_button.xml | 19 +++++++++++++++++++ .../drawable/bg_step_submit_button_ripple.xml | 7 +++++++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values/colors.xml | 5 +++++ app/src/main/res/values/dimens.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 53 insertions(+) create mode 100644 app/src/main/res/color/color_step_submit_button_text.xml create mode 100644 app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml create mode 100644 app/src/main/res/drawable/bg_step_submit_button.xml create mode 100644 app/src/main/res/drawable/bg_step_submit_button_ripple.xml diff --git a/app/src/main/res/color/color_step_submit_button_text.xml b/app/src/main/res/color/color_step_submit_button_text.xml new file mode 100644 index 0000000000..0b3a97f87d --- /dev/null +++ b/app/src/main/res/color/color_step_submit_button_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml b/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml new file mode 100644 index 0000000000..2cb7ffb4e1 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_submit_button.xml b/app/src/main/res/drawable/bg_step_submit_button.xml new file mode 100644 index 0000000000..271f134df3 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_submit_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_submit_button_ripple.xml b/app/src/main/res/drawable/bg_step_submit_button_ripple.xml new file mode 100644 index 0000000000..50b7451413 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_submit_button_ripple.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2602ccbbf1..1cefff67bf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -710,6 +710,8 @@ Видео не было добавлено или еще не обработано + Отправить + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e27779a562..73ee0b5c2f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -83,6 +83,7 @@ #80535366 #33FFFFFF #4DFFFFFF + #99FFFFFF #66CC66 #0D535366 @@ -151,6 +152,9 @@ #EAECF0 + #66CC66 + #54AD54 + #ACD1AC #227300 @@ -198,4 +202,5 @@ #2066CC66 @color/new_primary_color + @color/grey04 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 498bce5c13..00e786ea9f 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -215,4 +215,6 @@ 8dp @dimen/step_content_block_radius + + 8dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 175f4b1307..708dcc2765 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -740,6 +740,8 @@ Video has not been uploaded or processed yet + Submit + Choose email app From 23324c3f5cbe15148747eaf524d9e47871bcc080 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 20 Jun 2019 15:28:59 +0300 Subject: [PATCH 004/108] choice quiz fragment draft --- .../util/resolvers/StepTypeResolverImpl.java | 1 + .../ui/factory/StepQuizFragmentFactoryImpl.kt | 9 ++++++- .../ui/fragment/ChoiceStepQuizFragment.kt | 25 ++++++++++++++++++ .../res/layout/fragment_step_quiz_choice.xml | 26 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt create mode 100644 app/src/main/res/layout/fragment_step_quiz_choice.xml diff --git a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java index 97a196347b..3bdf2ab344 100644 --- a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java +++ b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java @@ -172,6 +172,7 @@ public boolean isNeedUseOldStepContainer(@NotNull Step step) { switch (step.getBlock().getName()) { case AppConstants.TYPE_TEXT: case AppConstants.TYPE_VIDEO: + case AppConstants.TYPE_CHOICE: return false; default: return true; diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index 356eb57e94..a9a2aa5cd7 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -3,13 +3,20 @@ package org.stepik.android.view.step_quiz.ui.factory import android.support.v4.app.Fragment import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants +import org.stepik.android.view.step_quiz_choice.ui.fragment.ChoiceStepQuizFragment import javax.inject.Inject class StepQuizFragmentFactoryImpl @Inject constructor() : StepQuizFragmentFactory { override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment = - Fragment() + when (stepPersistentWrapper.step.block?.name) { + AppConstants.TYPE_CHOICE -> + ChoiceStepQuizFragment.newInstance(stepPersistentWrapper) + else -> + Fragment() + + } override fun isStepCanHaveQuiz(stepPersistentWrapper: StepPersistentWrapper): Boolean = stepPersistentWrapper.step.block?.name != AppConstants.TYPE_VIDEO diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt new file mode 100644 index 0000000000..49439c4d1c --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -0,0 +1,25 @@ +package org.stepik.android.view.step_quiz_choice.ui.fragment + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.stepic.droid.R +import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.util.argument + +class ChoiceStepQuizFragment: Fragment() { + companion object { + fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = + ChoiceStepQuizFragment() + .apply { + this.stepWrapper = stepPersistentWrapper + } + } + + private var stepWrapper: StepPersistentWrapper by argument() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = + inflater.inflate(R.layout.fragment_step_quiz_choice, container, false) +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_choice.xml b/app/src/main/res/layout/fragment_step_quiz_choice.xml new file mode 100644 index 0000000000..bc6b6e8b47 --- /dev/null +++ b/app/src/main/res/layout/fragment_step_quiz_choice.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file From db069764f6c8e4d06db4a907b799ef586b7074d1 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 20 Jun 2019 15:30:59 +0300 Subject: [PATCH 005/108] fix step button --- .../ui/fragment/StringStepQuizFragment.kt | 8 ++++++++ .../bg_step_submit_button_ripple.xml | 4 ++-- .../res/drawable/bg_step_submit_button.xml | 19 ++++++++++--------- .../res/layout/fragment_step_quiz_string.xml | 12 +++--------- .../layout/view_step_quiz_submit_button.xml | 12 ++++++++++++ app/src/main/res/values-ru/strings.xml | 3 ++- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 3 ++- 8 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 app/src/main/res/layout/view_step_quiz_submit_button.xml diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt index 2c97438cae..7db964ef4b 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt @@ -5,6 +5,7 @@ import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument @@ -25,4 +26,11 @@ class StringStepQuizFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_step_quiz_string, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + stepQuizSubmit.setOnClickListener { } + stepQuizSubmit.isEnabled = true + } } \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml b/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml index 2cb7ffb4e1..76eb48a741 100644 --- a/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml +++ b/app/src/main/res/drawable-v21/bg_step_submit_button_ripple.xml @@ -1,10 +1,10 @@ + android:color="@color/green03"> - + diff --git a/app/src/main/res/drawable/bg_step_submit_button.xml b/app/src/main/res/drawable/bg_step_submit_button.xml index 271f134df3..4042db31c6 100644 --- a/app/src/main/res/drawable/bg_step_submit_button.xml +++ b/app/src/main/res/drawable/bg_step_submit_button.xml @@ -6,14 +6,15 @@ - - - - - + + + + + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml index bc6b6e8b47..5a6c5b9561 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -5,20 +5,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/app/src/main/res/layout/view_step_quiz_submit_button.xml b/app/src/main/res/layout/view_step_quiz_submit_button.xml new file mode 100644 index 0000000000..e902abbe9e --- /dev/null +++ b/app/src/main/res/layout/view_step_quiz_submit_button.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1cefff67bf..dfefb22158 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -710,7 +710,8 @@ Видео не было добавлено или еще не обработано - Отправить + Отправить + Попробовать снова Выберите почтовый клиент diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 00e786ea9f..2452ccfd66 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -217,4 +217,5 @@ @dimen/step_content_block_radius 8dp + 48dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 708dcc2765..adbba02bfc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -740,7 +740,8 @@ Video has not been uploaded or processed yet - Submit + Submit + Try again Choose email app From e683ccbed60b9271b14525225e8afb46c542c7f5 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 20 Jun 2019 16:30:13 +0300 Subject: [PATCH 006/108] add step content separator --- .../stepik/android/view/step/ui/fragment/StepFragment.kt | 1 + app/src/main/res/layout/fragment_step.xml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt index 10c139e0ed..dc8b1e03a3 100644 --- a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt @@ -127,6 +127,7 @@ class StepFragment : Fragment(), StepView { private fun initStepQuizFragment() { val isStepHasQuiz = stepQuizFragmentFactory.isStepCanHaveQuiz(stepWrapper) + stepContentSeparator.changeVisibility(isStepHasQuiz) stepQuizContainer.changeVisibility(isStepHasQuiz) if (isStepHasQuiz && childFragmentManager.findFragmentByTag(STEP_QUIZ_FRAGMENT_TAG) == null) { childFragmentManager diff --git a/app/src/main/res/layout/fragment_step.xml b/app/src/main/res/layout/fragment_step.xml index 17429db6bb..682dc5d264 100644 --- a/app/src/main/res/layout/fragment_step.xml +++ b/app/src/main/res/layout/fragment_step.xml @@ -17,6 +17,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + Date: Thu, 20 Jun 2019 20:49:19 +0300 Subject: [PATCH 007/108] string quiz field --- .../color_step_quiz_string_field_stroke.xml | 5 +++ .../drawable/bg_step_quiz_string_field.xml | 8 +++++ .../res/layout/fragment_step_quiz_string.xml | 36 +++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values/colors.xml | 2 ++ app/src/main/res/values/dimens.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 57 insertions(+) create mode 100644 app/src/main/res/color/color_step_quiz_string_field_stroke.xml create mode 100644 app/src/main/res/drawable/bg_step_quiz_string_field.xml diff --git a/app/src/main/res/color/color_step_quiz_string_field_stroke.xml b/app/src/main/res/color/color_step_quiz_string_field_stroke.xml new file mode 100644 index 0000000000..d175caa49e --- /dev/null +++ b/app/src/main/res/color/color_step_quiz_string_field_stroke.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_string_field.xml b/app/src/main/res/drawable/bg_step_quiz_string_field.xml new file mode 100644 index 0000000000..fbc5d64308 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_string_field.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml index 5a6c5b9561..37d5b511d9 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -2,9 +2,42 @@ + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index dfefb22158..260160b3d8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -713,6 +713,8 @@ Отправить Попробовать снова + Введите ответ… + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 73ee0b5c2f..48d5ae8dd7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -58,6 +58,7 @@ #F6F6F6 #535366 + #9a535366 #66535366 #4D535366 #0D535366 @@ -152,6 +153,7 @@ #EAECF0 + #CCCCCC #66CC66 #54AD54 #ACD1AC diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 2452ccfd66..c0dffd91b1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -218,4 +218,6 @@ 8dp 48dp + + 8dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index adbba02bfc..4179e9a4bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -743,6 +743,8 @@ Submit Try again + Type your answer… + Choose email app From 42b8a9ac95b30da57755691c10d1e5e2e5f5703f Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 21 Jun 2019 15:01:56 +0300 Subject: [PATCH 008/108] attempt data layers --- .../main/java/org/stepic/droid/web/Api.java | 1 + .../java/org/stepic/droid/web/ApiImpl.java | 2 ++ .../org/stepic/droid/web/AttemptRequest.kt | 9 ------- .../org/stepic/droid/web/AttemptResponse.kt | 7 ----- .../droid/web/StepicRestLoggedService.java | 2 ++ .../repository/AttemptRepositoryImpl.kt | 19 +++++++++++++ .../attempt/source/AttemptRemoteDataSource.kt | 9 +++++++ .../attempt/repository/AttemptRepository.kt | 9 +++++++ .../attempt/AttemptRemoteDataSourceImpl.kt | 25 +++++++++++++++++ .../remote/attempt/model/AttemptRequest.kt | 11 ++++++++ .../remote/attempt/model/AttemptResponse.kt | 13 +++++++++ .../injection/attempt/AttemptDataModule.kt | 21 +++++++++++++++ .../stepik/android/model/attempts/Attempt.kt | 27 +++++++++++-------- 13 files changed, 128 insertions(+), 27 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/web/AttemptRequest.kt delete mode 100644 app/src/main/java/org/stepic/droid/web/AttemptResponse.kt create mode 100644 app/src/main/java/org/stepik/android/data/attempt/repository/AttemptRepositoryImpl.kt create mode 100644 app/src/main/java/org/stepik/android/data/attempt/source/AttemptRemoteDataSource.kt create mode 100644 app/src/main/java/org/stepik/android/domain/attempt/repository/AttemptRepository.kt create mode 100644 app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt create mode 100644 app/src/main/java/org/stepik/android/remote/attempt/model/AttemptResponse.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/attempt/AttemptDataModule.kt diff --git a/app/src/main/java/org/stepic/droid/web/Api.java b/app/src/main/java/org/stepic/droid/web/Api.java index 4420db7ade..352bb4636d 100644 --- a/app/src/main/java/org/stepic/droid/web/Api.java +++ b/app/src/main/java/org/stepic/droid/web/Api.java @@ -18,6 +18,7 @@ import org.stepik.android.model.Tag; import org.stepik.android.model.Reply; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.attempt.model.AttemptResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; diff --git a/app/src/main/java/org/stepic/droid/web/ApiImpl.java b/app/src/main/java/org/stepic/droid/web/ApiImpl.java index b072d05945..769cefd270 100644 --- a/app/src/main/java/org/stepic/droid/web/ApiImpl.java +++ b/app/src/main/java/org/stepic/droid/web/ApiImpl.java @@ -67,6 +67,8 @@ import org.stepik.android.model.user.Profile; import org.stepik.android.model.user.RegistrationCredentials; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.attempt.model.AttemptRequest; +import org.stepik.android.remote.attempt.model.AttemptResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; diff --git a/app/src/main/java/org/stepic/droid/web/AttemptRequest.kt b/app/src/main/java/org/stepic/droid/web/AttemptRequest.kt deleted file mode 100644 index 349549e293..0000000000 --- a/app/src/main/java/org/stepic/droid/web/AttemptRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.stepic.droid.web - -import org.stepik.android.model.attempts.Attempt - -class AttemptRequest( - val attempt: Attempt -) { - constructor(stepId: Long): this(Attempt(step = stepId)) -} diff --git a/app/src/main/java/org/stepic/droid/web/AttemptResponse.kt b/app/src/main/java/org/stepic/droid/web/AttemptResponse.kt deleted file mode 100644 index 3b548054f4..0000000000 --- a/app/src/main/java/org/stepic/droid/web/AttemptResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.stepic.droid.web - -import org.stepik.android.model.attempts.Attempt - -class AttemptResponse( - val attempts: List -) diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 2e729f02f0..2a70caa1db 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -5,6 +5,8 @@ import org.stepic.droid.web.model.adaptive.RecommendationsResponse; import org.stepic.droid.web.model.story_templates.StoryTemplatesResponse; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.attempt.model.AttemptRequest; +import org.stepik.android.remote.attempt.model.AttemptResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; diff --git a/app/src/main/java/org/stepik/android/data/attempt/repository/AttemptRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/attempt/repository/AttemptRepositoryImpl.kt new file mode 100644 index 0000000000..751850cde4 --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/attempt/repository/AttemptRepositoryImpl.kt @@ -0,0 +1,19 @@ +package org.stepik.android.data.attempt.repository + +import io.reactivex.Single +import org.stepik.android.data.attempt.source.AttemptRemoteDataSource +import org.stepik.android.domain.attempt.repository.AttemptRepository +import org.stepik.android.model.attempts.Attempt +import javax.inject.Inject + +class AttemptRepositoryImpl +@Inject +constructor( + private val attemptRemoteDataSource: AttemptRemoteDataSource +) : AttemptRepository { + override fun createAttemptForStep(stepId: Long): Single = + attemptRemoteDataSource.createAttemptForStep(stepId) + + override fun getAttemptsForStep(stepId: Long): Single> = + attemptRemoteDataSource.getAttemptsForStep(stepId) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/data/attempt/source/AttemptRemoteDataSource.kt b/app/src/main/java/org/stepik/android/data/attempt/source/AttemptRemoteDataSource.kt new file mode 100644 index 0000000000..0ee23540f5 --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/attempt/source/AttemptRemoteDataSource.kt @@ -0,0 +1,9 @@ +package org.stepik.android.data.attempt.source + +import io.reactivex.Single +import org.stepik.android.model.attempts.Attempt + +interface AttemptRemoteDataSource { + fun createAttemptForStep(stepId: Long): Single + fun getAttemptsForStep(stepId: Long): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/attempt/repository/AttemptRepository.kt b/app/src/main/java/org/stepik/android/domain/attempt/repository/AttemptRepository.kt new file mode 100644 index 0000000000..501b0dda52 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/attempt/repository/AttemptRepository.kt @@ -0,0 +1,9 @@ +package org.stepik.android.domain.attempt.repository + +import io.reactivex.Single +import org.stepik.android.model.attempts.Attempt + +interface AttemptRepository { + fun createAttemptForStep(stepId: Long): Single + fun getAttemptsForStep(stepId: Long): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt new file mode 100644 index 0000000000..4506062dcd --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt @@ -0,0 +1,25 @@ +package org.stepik.android.remote.attempt + +import io.reactivex.Single +import org.stepic.droid.util.maybeFirst +import org.stepic.droid.web.Api +import org.stepik.android.data.attempt.source.AttemptRemoteDataSource +import org.stepik.android.model.attempts.Attempt +import org.stepik.android.remote.attempt.model.AttemptResponse +import javax.inject.Inject + +class AttemptRemoteDataSourceImpl +@Inject +constructor( + private val api: Api +) : AttemptRemoteDataSource { + override fun createAttemptForStep(stepId: Long): Single = + api.createNewAttemptReactive(stepId) + .map(AttemptResponse::attempts) + .maybeFirst() + .toSingle() + + override fun getAttemptsForStep(stepId: Long): Single> = + api.getExistingAttemptsReactive(stepId) + .map(AttemptResponse::attempts) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt new file mode 100644 index 0000000000..f16cbff11a --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt @@ -0,0 +1,11 @@ +package org.stepik.android.remote.attempt.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.attempts.Attempt + +class AttemptRequest( + @SerializedName("attempt") + val attempt: Attempt +) { + constructor(stepId: Long): this(Attempt(step = stepId)) +} diff --git a/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptResponse.kt b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptResponse.kt new file mode 100644 index 0000000000..15d3bb4199 --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptResponse.kt @@ -0,0 +1,13 @@ +package org.stepik.android.remote.attempt.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.Meta +import org.stepik.android.model.attempts.Attempt +import org.stepik.android.remote.base.model.MetaResponse + +class AttemptResponse( + @SerializedName("meta") + override val meta: Meta, + @SerializedName("attempts") + val attempts: List +) : MetaResponse diff --git a/app/src/main/java/org/stepik/android/view/injection/attempt/AttemptDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/attempt/AttemptDataModule.kt new file mode 100644 index 0000000000..1ef8ea8fc1 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/attempt/AttemptDataModule.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.injection.attempt + +import dagger.Binds +import dagger.Module +import org.stepik.android.data.attempt.repository.AttemptRepositoryImpl +import org.stepik.android.data.attempt.source.AttemptRemoteDataSource +import org.stepik.android.domain.attempt.repository.AttemptRepository +import org.stepik.android.remote.attempt.AttemptRemoteDataSourceImpl + +@Module +abstract class AttemptDataModule { + @Binds + internal abstract fun bindAttemptRepository( + attemptRepositoryImpl: AttemptRepositoryImpl + ): AttemptRepository + + @Binds + internal abstract fun bindAttemptRemoteDataSource( + attemptRemoteDataSourceImpl: AttemptRemoteDataSourceImpl + ): AttemptRemoteDataSource +} \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/attempts/Attempt.kt b/model/src/main/java/org/stepik/android/model/attempts/Attempt.kt index faae836449..6f4090fb79 100644 --- a/model/src/main/java/org/stepik/android/model/attempts/Attempt.kt +++ b/model/src/main/java/org/stepik/android/model/attempts/Attempt.kt @@ -3,20 +3,25 @@ package org.stepik.android.model.attempts import com.google.gson.annotations.SerializedName class Attempt( - val id: Long = 0, - val step: Long = 0, - val user: Long = 0, + @SerializedName("id") + val id: Long = 0, + @SerializedName("step") + val step: Long = 0, + @SerializedName("user") + val user: Long = 0, - @SerializedName("dataset") - private val _dataset: DatasetWrapper? = null, - @SerializedName("dataset_url") - val datasetUrl: String? = null, + @SerializedName("dataset") + private val _dataset: DatasetWrapper? = null, + @SerializedName("dataset_url") + val datasetUrl: String? = null, - val status: String? = null, - val time: String? = null, + @SerializedName("status") + val status: String? = null, + @SerializedName("time") + val time: String? = null, - @SerializedName("time_left") - val timeLeft: String? = null + @SerializedName("time_left") + val timeLeft: String? = null ) { val dataset: Dataset? get() = _dataset?.dataset From 29ef7946d389165cd5ba6cd7090fc226f37812cc Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 21 Jun 2019 17:36:44 +0300 Subject: [PATCH 009/108] add submissions data layer --- .../droid/core/presenters/CardPresenter.kt | 3 +- .../main/java/org/stepic/droid/util/RxUtil.kt | 24 +++++++++----- .../main/java/org/stepic/droid/web/Api.java | 5 ++- .../java/org/stepic/droid/web/ApiImpl.java | 9 +++++- .../droid/web/StepicRestLoggedService.java | 7 ++++- .../org/stepic/droid/web/SubmissionRequest.kt | 8 ----- .../stepic/droid/web/SubmissionResponse.java | 13 -------- .../repository/SubmissionRepositoryImpl.kt | 22 +++++++++++++ .../source/SubmissionRemoteDataSource.kt | 10 ++++++ .../repository/SubmissionRepository.kt | 10 ++++++ .../SubmissionRemoteDataSourceImpl.kt | 31 +++++++++++++++++++ .../submission/model/SubmissionRequest.kt | 12 +++++++ .../submission/model/SubmissionResponse.kt | 13 ++++++++ .../submission/SubmissionDataModule.kt | 21 +++++++++++++ .../org/stepik/android/model/Submission.kt | 26 ++++++++++------ 15 files changed, 172 insertions(+), 42 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/web/SubmissionRequest.kt delete mode 100644 app/src/main/java/org/stepic/droid/web/SubmissionResponse.java create mode 100644 app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt create mode 100644 app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt create mode 100644 app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt create mode 100644 app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/stepik/android/remote/submission/model/SubmissionRequest.kt create mode 100644 app/src/main/java/org/stepik/android/remote/submission/model/SubmissionResponse.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/submission/SubmissionDataModule.kt diff --git a/app/src/main/java/org/stepic/droid/core/presenters/CardPresenter.kt b/app/src/main/java/org/stepic/droid/core/presenters/CardPresenter.kt index 2ef8783668..4b76a7df3e 100644 --- a/app/src/main/java/org/stepic/droid/core/presenters/CardPresenter.kt +++ b/app/src/main/java/org/stepic/droid/core/presenters/CardPresenter.kt @@ -16,7 +16,7 @@ import org.stepic.droid.di.qualifiers.BackgroundScheduler import org.stepic.droid.di.qualifiers.MainScheduler import org.stepic.droid.util.getStepType import org.stepic.droid.web.Api -import org.stepic.droid.web.SubmissionResponse +import org.stepik.android.remote.submission.model.SubmissionResponse import retrofit2.HttpException import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -114,6 +114,7 @@ class CardPresenter( reply = view?.getQuizViewDelegate()?.createReply(), attempt = card.attempt?.id ?: 0) disposable = api.createNewSubmissionReactive(submission) + .ignoreElement() .andThen(api.getSubmissionsReactive(submission.attempt)) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) diff --git a/app/src/main/java/org/stepic/droid/util/RxUtil.kt b/app/src/main/java/org/stepic/droid/util/RxUtil.kt index 1827edb900..e375a0afa4 100644 --- a/app/src/main/java/org/stepic/droid/util/RxUtil.kt +++ b/app/src/main/java/org/stepic/droid/util/RxUtil.kt @@ -65,13 +65,6 @@ class RetryExponential(private val maxAttempts: Int) } - -fun Iterable.toMaybe(): Maybe = - firstOrNull()?.let { Maybe.just(it) } ?: Maybe.empty() - -fun Single>.maybeFirst(): Maybe = - flatMapMaybe { it.toMaybe() } - inline fun Maybe.doCompletableOnSuccess(crossinline completableSource: (T) -> Completable): Maybe = flatMap { completableSource(it).andThen(Maybe.just(it)) } @@ -101,9 +94,24 @@ fun Observable.filterSingle(predicateSource: (T) -> Single): Obs .map { item } } +/** + * Wraps current object to Maybe + */ fun T?.toMaybe(): Maybe = if (this == null) { Maybe.empty() } else { Maybe.just(this) - } \ No newline at end of file + } + +/** + * Returns first element of list wrapped in Maybe or empty + */ +fun Single>.maybeFirst(): Maybe = + flatMapMaybe { it.firstOrNull().toMaybe() } + +/** + * Returns first element of list + */ +fun Single>.first(): Single = + map { it.first() } \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/Api.java b/app/src/main/java/org/stepic/droid/web/Api.java index 352bb4636d..cb956b7af0 100644 --- a/app/src/main/java/org/stepic/droid/web/Api.java +++ b/app/src/main/java/org/stepic/droid/web/Api.java @@ -28,6 +28,7 @@ import org.stepik.android.remote.progress.model.ProgressResponse; import org.stepik.android.remote.section.model.SectionResponse; import org.stepik.android.remote.step.model.StepResponse; +import org.stepik.android.remote.submission.model.SubmissionResponse; import org.stepik.android.remote.unit.model.UnitResponse; import org.stepik.android.remote.user.model.UserResponse; @@ -119,7 +120,7 @@ enum TokenType { Call createNewSubmission(Reply reply, long attemptId); - Completable createNewSubmissionReactive(Submission submission); + Single createNewSubmissionReactive(Submission submission); Call getExistingAttempts(long stepId); @@ -131,6 +132,8 @@ enum TokenType { Call getSubmissionForStep(long stepId); + Single getSubmissionForStepReactive(long stepId); + Call remindPassword(String email); Call getEmailAddresses(long[] ids); diff --git a/app/src/main/java/org/stepic/droid/web/ApiImpl.java b/app/src/main/java/org/stepic/droid/web/ApiImpl.java index 769cefd270..d998362374 100644 --- a/app/src/main/java/org/stepic/droid/web/ApiImpl.java +++ b/app/src/main/java/org/stepic/droid/web/ApiImpl.java @@ -78,6 +78,8 @@ import org.stepik.android.remote.progress.model.ProgressResponse; import org.stepik.android.remote.section.model.SectionResponse; import org.stepik.android.remote.step.model.StepResponse; +import org.stepik.android.remote.submission.model.SubmissionRequest; +import org.stepik.android.remote.submission.model.SubmissionResponse; import org.stepik.android.remote.unit.model.UnitResponse; import org.stepik.android.remote.user.model.UserResponse; @@ -654,7 +656,7 @@ public Call createNewSubmission(Reply reply, long attemptId) } @Override - public Completable createNewSubmissionReactive(Submission submission) { + public Single createNewSubmissionReactive(Submission submission) { return loggedService.createNewSubmissionReactive(new SubmissionRequest(submission)); } @@ -683,6 +685,11 @@ public Call getSubmissionForStep(long stepId) { return loggedService.getExistingSubmissionsForStep(stepId); } + @Override + public Single getSubmissionForStepReactive(long stepId) { + return loggedService.getExistingSubmissionsForStepReactive(stepId); + } + @Override public Call remindPassword(String email) { String encodedEmail = URLEncoder.encode(email); diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 2a70caa1db..ad6eddeeb9 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -20,6 +20,8 @@ import org.stepik.android.remote.progress.model.ProgressResponse; import org.stepik.android.remote.section.model.SectionResponse; import org.stepik.android.remote.step.model.StepResponse; +import org.stepik.android.remote.submission.model.SubmissionRequest; +import org.stepik.android.remote.submission.model.SubmissionResponse; import org.stepik.android.remote.unit.model.UnitResponse; import org.stepik.android.remote.user.model.UserResponse; import org.stepik.android.remote.view_assignment.model.ViewAssignmentRequest; @@ -157,7 +159,7 @@ Call createNewSubmission( ); @POST("api/submissions") - Completable createNewSubmissionReactive( + Single createNewSubmissionReactive( @Body SubmissionRequest submissionRequest ); @@ -230,6 +232,9 @@ Single getExistingSubmissionsReactive( @GET("api/submissions?order=desc") Call getExistingSubmissionsForStep(@Query("step") long stepId); + @GET("api/submissions?order=desc") + Single getExistingSubmissionsForStepReactive(@Query("step") long stepId); + @GET("api/notifications") Call getNotifications(@Query("page") int page, @Nullable @Query("type") String type); diff --git a/app/src/main/java/org/stepic/droid/web/SubmissionRequest.kt b/app/src/main/java/org/stepic/droid/web/SubmissionRequest.kt deleted file mode 100644 index 68178182de..0000000000 --- a/app/src/main/java/org/stepic/droid/web/SubmissionRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.stepic.droid.web - -import org.stepik.android.model.Submission -import org.stepik.android.model.Reply - -class SubmissionRequest(val submission: Submission) { - constructor(reply: Reply, attemptId: Long) : this(Submission(reply = reply, attempt = attemptId)) -} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/SubmissionResponse.java b/app/src/main/java/org/stepic/droid/web/SubmissionResponse.java deleted file mode 100644 index edf49f8454..0000000000 --- a/app/src/main/java/org/stepic/droid/web/SubmissionResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.stepic.droid.web; - -import org.stepik.android.model.Submission; - -import java.util.List; - -public class SubmissionResponse { - public List getSubmissions() { - return submissions; - } - - List submissions; -} diff --git a/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt new file mode 100644 index 0000000000..b4947dd99a --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt @@ -0,0 +1,22 @@ +package org.stepik.android.data.submission.repository + +import io.reactivex.Single +import org.stepik.android.data.submission.source.SubmissionRemoteDataSource +import org.stepik.android.domain.submission.repository.SubmissionRepository +import org.stepik.android.model.Submission +import javax.inject.Inject + +class SubmissionRepositoryImpl +@Inject +constructor( + private val submissionRemoteDataSource: SubmissionRemoteDataSource +) : SubmissionRepository { + override fun createSubmission(submission: Submission): Single = + submissionRemoteDataSource.createSubmission(submission) + + override fun getSubmissionsForAttempt(attemptId: Long): Single> = + submissionRemoteDataSource.getSubmissionsForAttemtp(attemptId) + + override fun getSubmissionsForStep(stepId: Long): Single> = + submissionRemoteDataSource.getSubmissionsForStep(stepId) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt b/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt new file mode 100644 index 0000000000..68b7021dd9 --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt @@ -0,0 +1,10 @@ +package org.stepik.android.data.submission.source + +import io.reactivex.Single +import org.stepik.android.model.Submission + +interface SubmissionRemoteDataSource { + fun createSubmission(submission: Submission): Single + fun getSubmissionsForAttemtp(attemptId: Long): Single> + fun getSubmissionsForStep(stepId: Long): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt new file mode 100644 index 0000000000..a38726cff6 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt @@ -0,0 +1,10 @@ +package org.stepik.android.domain.submission.repository + +import io.reactivex.Single +import org.stepik.android.model.Submission + +interface SubmissionRepository { + fun createSubmission(submission: Submission): Single + fun getSubmissionsForAttempt(attemptId: Long): Single> + fun getSubmissionsForStep(stepId: Long): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt new file mode 100644 index 0000000000..c2cd5929b8 --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt @@ -0,0 +1,31 @@ +package org.stepik.android.remote.submission + +import io.reactivex.Single +import io.reactivex.functions.Function +import org.stepic.droid.util.first +import org.stepic.droid.web.Api +import org.stepik.android.data.submission.source.SubmissionRemoteDataSource +import org.stepik.android.model.Submission +import org.stepik.android.remote.submission.model.SubmissionResponse +import javax.inject.Inject + +class SubmissionRemoteDataSourceImpl +@Inject +constructor( + private val api: Api +) : SubmissionRemoteDataSource { + private val submissionMapper = Function(SubmissionResponse::submissions) + + override fun createSubmission(submission: Submission): Single = + api.createNewSubmissionReactive(submission) + .map(submissionMapper) + .first() + + override fun getSubmissionsForAttemtp(attemptId: Long): Single> = + api.getSubmissionsReactive(attemptId) + .map(submissionMapper) + + override fun getSubmissionsForStep(stepId: Long): Single> = + api.getSubmissionForStepReactive(stepId) + .map(submissionMapper) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionRequest.kt b/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionRequest.kt new file mode 100644 index 0000000000..679e37865f --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionRequest.kt @@ -0,0 +1,12 @@ +package org.stepik.android.remote.submission.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.Submission +import org.stepik.android.model.Reply + +class SubmissionRequest( + @SerializedName("submission") + val submission: Submission +) { + constructor(reply: Reply, attemptId: Long) : this(Submission(reply = reply, attempt = attemptId)) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionResponse.kt b/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionResponse.kt new file mode 100644 index 0000000000..850b7e78a4 --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/submission/model/SubmissionResponse.kt @@ -0,0 +1,13 @@ +package org.stepik.android.remote.submission.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.Meta +import org.stepik.android.model.Submission +import org.stepik.android.remote.base.model.MetaResponse + +class SubmissionResponse( + @SerializedName("meta") + override val meta: Meta, + @SerializedName("submissions") + val submissions: List +) : MetaResponse \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/submission/SubmissionDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/submission/SubmissionDataModule.kt new file mode 100644 index 0000000000..ab8dcd6d64 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/submission/SubmissionDataModule.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.injection.submission + +import dagger.Binds +import dagger.Module +import org.stepik.android.data.submission.repository.SubmissionRepositoryImpl +import org.stepik.android.data.submission.source.SubmissionRemoteDataSource +import org.stepik.android.domain.submission.repository.SubmissionRepository +import org.stepik.android.remote.submission.SubmissionRemoteDataSourceImpl + +@Module +abstract class SubmissionDataModule { + @Binds + internal abstract fun bindSubmissionRepository( + submissionRepositoryImpl: SubmissionRepositoryImpl + ): SubmissionRepository + + @Binds + internal abstract fun bindSubmissionRemoteDataSource( + submissionRemoteDataSourceImpl: SubmissionRemoteDataSourceImpl + ): SubmissionRemoteDataSource +} \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/Submission.kt b/model/src/main/java/org/stepik/android/model/Submission.kt index 79a4855988..7e7f9220b0 100644 --- a/model/src/main/java/org/stepik/android/model/Submission.kt +++ b/model/src/main/java/org/stepik/android/model/Submission.kt @@ -3,15 +3,23 @@ package org.stepik.android.model import com.google.gson.annotations.SerializedName class Submission( - val id: Long = 0, - val status: Status? = null, - val score: String? = null, - val hint: String? = null, - val time: String? = null, - reply: Reply? = null, - val attempt: Long = 0, - val session: String? = null, - val eta: String? = null + @SerializedName("id") + val id: Long = 0, + @SerializedName("status") + val status: Status? = null, + @SerializedName("score") + val score: String? = null, + @SerializedName("hint") + val hint: String? = null, + @SerializedName("time") + val time: String? = null, + reply: Reply? = null, + @SerializedName("attempt") + val attempt: Long = 0, + @SerializedName("session") + val session: String? = null, + @SerializedName("eta") + val eta: String? = null ) { @Deprecated("this compatibility constructor will be removed after rewriting StepAttemptFragment in Kotlin") constructor(reply: Reply?, attempt: Long, status: Status?): this(id = 0, reply = reply, attempt = attempt, status = status) From 706449f56ebc2765fe8bc6d383672e124e1394a7 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 21 Jun 2019 17:47:12 +0300 Subject: [PATCH 010/108] code optimizations --- .../android/cache/profile/ProfileCacheDataSourceImpl.kt | 7 ++++--- .../domain/step/interactor/StepNavigationInteractor.kt | 4 ++-- .../android/remote/attempt/AttemptRemoteDataSourceImpl.kt | 7 +++++-- .../stepik/android/remote/attempt/model/AttemptRequest.kt | 2 +- .../personal_deadlines/DeadlinesRemoteDataSourceImpl.kt | 4 ++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/stepik/android/cache/profile/ProfileCacheDataSourceImpl.kt b/app/src/main/java/org/stepik/android/cache/profile/ProfileCacheDataSourceImpl.kt index f3050914b0..df1a3a0669 100644 --- a/app/src/main/java/org/stepik/android/cache/profile/ProfileCacheDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/cache/profile/ProfileCacheDataSourceImpl.kt @@ -3,6 +3,7 @@ package org.stepik.android.cache.profile import io.reactivex.Completable import io.reactivex.Maybe import org.stepic.droid.preferences.SharedPreferenceHelper +import org.stepic.droid.util.toMaybe import org.stepik.android.data.profile.source.ProfileCacheDataSource import org.stepik.android.model.user.Profile import javax.inject.Inject @@ -13,9 +14,9 @@ constructor( private val sharedPreferenceHelper: SharedPreferenceHelper ) : ProfileCacheDataSource { override fun getProfile(): Maybe = - sharedPreferenceHelper.profile - ?.let { Maybe.just(it) } - ?: Maybe.empty() + sharedPreferenceHelper + .profile + .toMaybe() override fun saveProfile(profile: Profile): Completable = Completable.fromAction { diff --git a/app/src/main/java/org/stepik/android/domain/step/interactor/StepNavigationInteractor.kt b/app/src/main/java/org/stepik/android/domain/step/interactor/StepNavigationInteractor.kt index a6093a9e57..4d1f8c60a0 100644 --- a/app/src/main/java/org/stepik/android/domain/step/interactor/StepNavigationInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/step/interactor/StepNavigationInteractor.kt @@ -11,6 +11,7 @@ import org.stepik.android.domain.unit.repository.UnitRepository import org.stepik.android.model.Step import org.stepic.droid.util.filterSingle import org.stepic.droid.util.hasUserAccessAndNotEmpty +import org.stepic.droid.util.toMaybe import org.stepik.android.model.Course import org.stepik.android.model.Lesson import org.stepik.android.model.Section @@ -70,8 +71,7 @@ constructor( .flatMapMaybe { sections -> sections .firstOrNull { it.hasUserAccessAndNotEmpty(lessonData.course) } - ?.let { Maybe.just(it) } - ?: Maybe.empty() + .toMaybe() } .flatMap { section -> val unitId = diff --git a/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt index 4506062dcd..14b2166e8b 100644 --- a/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt @@ -1,6 +1,7 @@ package org.stepik.android.remote.attempt import io.reactivex.Single +import io.reactivex.functions.Function import org.stepic.droid.util.maybeFirst import org.stepic.droid.web.Api import org.stepik.android.data.attempt.source.AttemptRemoteDataSource @@ -13,13 +14,15 @@ class AttemptRemoteDataSourceImpl constructor( private val api: Api ) : AttemptRemoteDataSource { + private val attemptMapper = Function(AttemptResponse::attempts) + override fun createAttemptForStep(stepId: Long): Single = api.createNewAttemptReactive(stepId) - .map(AttemptResponse::attempts) + .map(attemptMapper) .maybeFirst() .toSingle() override fun getAttemptsForStep(stepId: Long): Single> = api.getExistingAttemptsReactive(stepId) - .map(AttemptResponse::attempts) + .map(attemptMapper) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt index f16cbff11a..28ec40d500 100644 --- a/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt +++ b/app/src/main/java/org/stepik/android/remote/attempt/model/AttemptRequest.kt @@ -7,5 +7,5 @@ class AttemptRequest( @SerializedName("attempt") val attempt: Attempt ) { - constructor(stepId: Long): this(Attempt(step = stepId)) + constructor(stepId: Long) : this(Attempt(step = stepId)) } diff --git a/app/src/main/java/org/stepik/android/remote/personal_deadlines/DeadlinesRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/personal_deadlines/DeadlinesRemoteDataSourceImpl.kt index dffa66005a..b248254e9f 100644 --- a/app/src/main/java/org/stepik/android/remote/personal_deadlines/DeadlinesRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/remote/personal_deadlines/DeadlinesRemoteDataSourceImpl.kt @@ -6,6 +6,7 @@ import io.reactivex.Single import org.stepik.android.domain.personal_deadlines.model.DeadlinesWrapper import org.stepik.android.data.personal_deadlines.getKindOfRecord import org.stepic.droid.preferences.SharedPreferenceHelper +import org.stepic.droid.util.toMaybe import org.stepic.droid.web.storage.RemoteStorageService import org.stepic.droid.web.storage.model.StorageRecord import org.stepik.android.data.personal_deadlines.source.DeadlinesRemoteDataSource @@ -41,7 +42,6 @@ constructor( .flatMap { response -> deadlinesMapper .mapToStorageRecord(response) - ?.let { Maybe.just(it) } - ?: Maybe.empty() + .toMaybe() } } \ No newline at end of file From f9bfd11e6c0a6458a6c628b6ec2d3cb053766424 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Mon, 24 Jun 2019 16:25:40 +0300 Subject: [PATCH 011/108] backgrounds for choice quiz adapter items --- .../main/res/drawable/choice_checked_background.xml | 13 +++++++++++++ .../main/res/drawable/choice_correct_background.xml | 13 +++++++++++++ .../res/drawable/choice_incorrect_background.xml | 13 +++++++++++++ .../res/drawable/choice_not_checked_background.xml | 13 +++++++++++++ app/src/main/res/values/colors.xml | 2 ++ dependencies.gradle | 1 + 6 files changed, 55 insertions(+) create mode 100644 app/src/main/res/drawable/choice_checked_background.xml create mode 100644 app/src/main/res/drawable/choice_correct_background.xml create mode 100644 app/src/main/res/drawable/choice_incorrect_background.xml create mode 100644 app/src/main/res/drawable/choice_not_checked_background.xml diff --git a/app/src/main/res/drawable/choice_checked_background.xml b/app/src/main/res/drawable/choice_checked_background.xml new file mode 100644 index 0000000000..03322a66c5 --- /dev/null +++ b/app/src/main/res/drawable/choice_checked_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/choice_correct_background.xml b/app/src/main/res/drawable/choice_correct_background.xml new file mode 100644 index 0000000000..8b4f955048 --- /dev/null +++ b/app/src/main/res/drawable/choice_correct_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/choice_incorrect_background.xml b/app/src/main/res/drawable/choice_incorrect_background.xml new file mode 100644 index 0000000000..58a9456e29 --- /dev/null +++ b/app/src/main/res/drawable/choice_incorrect_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/choice_not_checked_background.xml b/app/src/main/res/drawable/choice_not_checked_background.xml new file mode 100644 index 0000000000..325bb72d4c --- /dev/null +++ b/app/src/main/res/drawable/choice_not_checked_background.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 83a582fc30..38a890c294 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -200,10 +200,12 @@ @color/new_primary_color + #ffffff #e8eafa #e7f7e7 #ffebe7 + #888888 @color/color_violet_1 #64cc64 #ff7965 diff --git a/dependencies.gradle b/dependencies.gradle index 358d77f990..f61e594c04 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -70,6 +70,7 @@ ext.versions = [ confetti : '1.1.1', MPAndroidChart : 'v3.0.3', + shortcutBadger : '1.1.19@aar', StoriesKit : '1.1.1', From 9b3f3023dccfbf5f08535731a752f74cbf2fc543 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Mon, 24 Jun 2019 16:40:49 +0300 Subject: [PATCH 012/108] added adapter libraries --- app/build.gradle | 2 ++ dependencies.gradle | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b103086d92..184c1f8c97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,6 +189,8 @@ dependencies { implementation libraries.shortcutBadger implementation libraries.StoriesKit + implementation libraries.AdapterDelegates + implementation libraries.Adapters debugImplementation libraries.leakCanary releaseImplementation libraries.leakCanaryNoOp diff --git a/dependencies.gradle b/dependencies.gradle index f61e594c04..a919e65857 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -70,9 +70,10 @@ ext.versions = [ confetti : '1.1.1', MPAndroidChart : 'v3.0.3', - shortcutBadger : '1.1.19@aar', StoriesKit : '1.1.1', + AdapterDelegates : '1.0.1', + Adapters : '1.0.1', stetho : '1.4.2', leakCanary : '1.5.1', @@ -174,6 +175,8 @@ ext.libraries = [ shortcutBadger : "me.leolin:ShortcutBadger:$versions.shortcutBadger", StoriesKit : "ru.nobird.android:StoriesKitSupport:$versions.StoriesKit", + AdapterDelegates : "ru.nobird.android.ui:AdapterDelegates:$versions.AdapterDelegates", + Adapters : "ru.nobird.android.ui:Adapters:$versions.Adapters", // Developer Tools leakCanary : "com.squareup.leakcanary:leakcanary-android:$versions.leakCanary", From 10ff89dfcf45d2d4e025e9497e5e883d0f67d891 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 24 Jun 2019 18:32:48 +0300 Subject: [PATCH 013/108] add step evaluation --- .../main/java/org/stepic/droid/base/App.kt | 13 ----- .../ui/fragment/StringStepQuizFragment.kt | 9 ++++ .../res/drawable/bg_step_quiz_progress.xml | 7 +++ .../res/drawable/ic_step_quiz_progress.xml | 7 +++ .../ic_step_quiz_progress_frame_1.xml | 31 ++++++++++++ .../ic_step_quiz_progress_frame_2.xml | 31 ++++++++++++ .../ic_step_quiz_progress_frame_3.xml | 31 ++++++++++++ .../res/layout/fragment_step_quiz_string.xml | 47 +++++++++++++++---- app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values/dimens.xml | 3 +- app/src/main/res/values/strings.xml | 2 + 11 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 app/src/main/res/drawable/bg_step_quiz_progress.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_progress.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml diff --git a/app/src/main/java/org/stepic/droid/base/App.kt b/app/src/main/java/org/stepic/droid/base/App.kt index 768458c701..f6cafcc6d9 100644 --- a/app/src/main/java/org/stepic/droid/base/App.kt +++ b/app/src/main/java/org/stepic/droid/base/App.kt @@ -99,19 +99,6 @@ class App : MultiDexApplication() { if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) - StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectAll() - .penaltyLog() - .build()) - StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() - .detectLeakedSqlLiteObjects() - .detectLeakedClosableObjects() - .penaltyLog() - .penaltyDeath() - .build()) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WebView.setDataDirectorySuffix("web") diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt index 7db964ef4b..5415ae7da3 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt @@ -1,13 +1,18 @@ package org.stepik.android.view.step_quiz_string.ui.fragment +import android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.support.v4.app.Fragment +import android.support.v7.content.res.AppCompatResources +import android.support.v7.graphics.drawable.AnimatedStateListDrawableCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_step_quiz_string.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.ui.util.setCompoundDrawables import org.stepic.droid.util.argument import org.stepik.android.domain.lesson.model.LessonData @@ -32,5 +37,9 @@ class StringStepQuizFragment : Fragment() { stepQuizSubmit.setOnClickListener { } stepQuizSubmit.isEnabled = true + + val drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_step_quiz_progress) as? AnimationDrawable + stepQuizProgress.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + drawable?.start() } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_progress.xml b/app/src/main/res/drawable/bg_step_quiz_progress.xml new file mode 100644 index 0000000000..6d4d19b94f --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_progress.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_progress.xml b/app/src/main/res/drawable/ic_step_quiz_progress.xml new file mode 100644 index 0000000000..998e2b85fe --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_progress.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml b/app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml new file mode 100644 index 0000000000..1b882dd90f --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml b/app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml new file mode 100644 index 0000000000..3af271f583 --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml b/app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml new file mode 100644 index 0000000000..2617e089ae --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml index 37d5b511d9..b8ebc9cbe0 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -24,31 +24,60 @@ android:textColorHint="@color/new_accent_color_alpha_40" android:gravity="center_vertical" - android:paddingStart="16dp" - android:paddingEnd="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" android:paddingTop="12dp" android:paddingBottom="12dp" android:layout_marginTop="16dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" tools:targetApi="lollipop" /> - + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 260160b3d8..f764487588 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -715,6 +715,8 @@ Введите ответ… + Проверка… + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c0dffd91b1..09a602140d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -216,7 +216,8 @@ 8dp @dimen/step_content_block_radius - 8dp + 8dp + @dimen/step_control_block_radius 48dp 8dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4179e9a4bb..2992fdb5f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -745,6 +745,8 @@ Type your answer… + Evaluation… + Choose email app From 4a44e26cd7a4ff8cd8616bb919ce0fdc27f012a6 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Tue, 25 Jun 2019 12:22:27 +0300 Subject: [PATCH 014/108] fix submit button for android 4 --- .../drawable/bg_step_submit_button_ripple.xml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/drawable/bg_step_submit_button_ripple.xml b/app/src/main/res/drawable/bg_step_submit_button_ripple.xml index 50b7451413..207766213b 100644 --- a/app/src/main/res/drawable/bg_step_submit_button_ripple.xml +++ b/app/src/main/res/drawable/bg_step_submit_button_ripple.xml @@ -1,7 +1,11 @@ - - - - \ No newline at end of file + + + + + + + + \ No newline at end of file From 143ecd494c200568219e9ebd0afa57a4d095711a Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Tue, 25 Jun 2019 13:44:50 +0300 Subject: [PATCH 015/108] basic feedback layouts --- .../ui/fragment/StringStepQuizFragment.kt | 11 +++- .../bg_step_quiz_feedback_correct.xml | 7 +++ ...l => bg_step_quiz_feedback_evaluation.xml} | 0 .../drawable/bg_step_quiz_feedback_wrong.xml | 7 +++ .../res/drawable/ic_step_quiz_correct.xml | 12 ++++ .../res/drawable/ic_step_quiz_evaluation.xml | 7 +++ ...ml => ic_step_quiz_evaluation_frame_1.xml} | 0 ...ml => ic_step_quiz_evaluation_frame_2.xml} | 0 ...ml => ic_step_quiz_evaluation_frame_3.xml} | 0 .../res/drawable/ic_step_quiz_progress.xml | 7 --- .../main/res/drawable/ic_step_quiz_wrong.xml | 18 ++++++ .../res/layout/fragment_step_quiz_string.xml | 59 ++++++++++++------- app/src/main/res/values-ru/arrays.xml | 17 ++++++ app/src/main/res/values-ru/strings.xml | 5 +- app/src/main/res/values/arrays.xml | 17 ++++++ app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/styles.xml | 16 +++++ 18 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 app/src/main/res/drawable/bg_step_quiz_feedback_correct.xml rename app/src/main/res/drawable/{bg_step_quiz_progress.xml => bg_step_quiz_feedback_evaluation.xml} (100%) create mode 100644 app/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_correct.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_evaluation.xml rename app/src/main/res/drawable/{ic_step_quiz_progress_frame_1.xml => ic_step_quiz_evaluation_frame_1.xml} (100%) rename app/src/main/res/drawable/{ic_step_quiz_progress_frame_2.xml => ic_step_quiz_evaluation_frame_2.xml} (100%) rename app/src/main/res/drawable/{ic_step_quiz_progress_frame_3.xml => ic_step_quiz_evaluation_frame_3.xml} (100%) delete mode 100644 app/src/main/res/drawable/ic_step_quiz_progress.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_wrong.xml diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt index 5415ae7da3..6393a55973 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt @@ -4,7 +4,6 @@ import android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.support.v4.app.Fragment import android.support.v7.content.res.AppCompatResources -import android.support.v7.graphics.drawable.AnimatedStateListDrawableCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -38,8 +37,14 @@ class StringStepQuizFragment : Fragment() { stepQuizSubmit.setOnClickListener { } stepQuizSubmit.isEnabled = true - val drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_step_quiz_progress) as? AnimationDrawable - stepQuizProgress.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + val drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_step_quiz_evaluation) as? AnimationDrawable + stepQuizFeedbackEvaluation.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) drawable?.start() + + stepQuizFeedbackCorrect.setCompoundDrawables(start = R.drawable.ic_step_quiz_correct) + stepQuizFeedbackCorrect.text = resources.getStringArray(R.array.step_quiz_feedback_correct).random() + + stepQuizFeedbackWrong.setCompoundDrawables(start = R.drawable.ic_step_quiz_wrong) + stepQuizFeedbackWrong.setText(R.string.step_quiz_feedback_wrong_not_last_try) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_feedback_correct.xml b/app/src/main/res/drawable/bg_step_quiz_feedback_correct.xml new file mode 100644 index 0000000000..d6cf595739 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_feedback_correct.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_progress.xml b/app/src/main/res/drawable/bg_step_quiz_feedback_evaluation.xml similarity index 100% rename from app/src/main/res/drawable/bg_step_quiz_progress.xml rename to app/src/main/res/drawable/bg_step_quiz_feedback_evaluation.xml diff --git a/app/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml b/app/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml new file mode 100644 index 0000000000..0951abbb4a --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_feedback_wrong.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_correct.xml b/app/src/main/res/drawable/ic_step_quiz_correct.xml new file mode 100644 index 0000000000..244b411bfd --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_correct.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_step_quiz_evaluation.xml b/app/src/main/res/drawable/ic_step_quiz_evaluation.xml new file mode 100644 index 0000000000..02eb701f77 --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_evaluation.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml b/app/src/main/res/drawable/ic_step_quiz_evaluation_frame_1.xml similarity index 100% rename from app/src/main/res/drawable/ic_step_quiz_progress_frame_1.xml rename to app/src/main/res/drawable/ic_step_quiz_evaluation_frame_1.xml diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml b/app/src/main/res/drawable/ic_step_quiz_evaluation_frame_2.xml similarity index 100% rename from app/src/main/res/drawable/ic_step_quiz_progress_frame_2.xml rename to app/src/main/res/drawable/ic_step_quiz_evaluation_frame_2.xml diff --git a/app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml b/app/src/main/res/drawable/ic_step_quiz_evaluation_frame_3.xml similarity index 100% rename from app/src/main/res/drawable/ic_step_quiz_progress_frame_3.xml rename to app/src/main/res/drawable/ic_step_quiz_evaluation_frame_3.xml diff --git a/app/src/main/res/drawable/ic_step_quiz_progress.xml b/app/src/main/res/drawable/ic_step_quiz_progress.xml deleted file mode 100644 index 998e2b85fe..0000000000 --- a/app/src/main/res/drawable/ic_step_quiz_progress.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_wrong.xml b/app/src/main/res/drawable/ic_step_quiz_wrong.xml new file mode 100644 index 0000000000..e4bed21c28 --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_wrong.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml index b8ebc9cbe0..0b49689009 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -32,6 +32,7 @@ android:layout_marginTop="16dp" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -39,36 +40,54 @@ tools:targetApi="lollipop" /> + + + + + tools:drawableStart="@drawable/ic_step_quiz_evaluation" /> Последние 7 дней За всё время + + + Правильно + Правильно, молодец! + Всё правильно + Верно + Верно. Так держать! + Здорово, всё верно + Хорошая работа + Отличное решение! + Абсолютно точно + Хорошие новости, верно! + Прекрасный ответ + Так точно! + Отлично! + Всё получилось! + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f764487588..2126753607 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -715,7 +715,10 @@ Введите ответ… - Проверка… + Проверка… + Любой ответ будет оценен как правильный + Пока неправильно, попробуйте еще раз! + Неверно Выберите почтовый клиент diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 83d5ede081..e5084942f3 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -24,4 +24,21 @@ #ACA5FF #ACECFE + + + Correct + You are right, well done! + All is correct + Right + You\'re right! + Totally right + Good job + Great work! + Absolutely right + Good news for you, correct! + Fabulous answer + Yes! + Great! + Well done! + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 48d5ae8dd7..a502578b61 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -205,4 +205,7 @@ @color/new_primary_color @color/grey04 + + #ff7965 + #26ff7965 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2992fdb5f3..c49ee15add 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -745,7 +745,10 @@ Type your answer… - Evaluation… + Evaluation… + Any text response will be graded as correct + Wrong. Let\'s try again. + Incorrect Choose email app diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5fbdc4625e..fdc3b8f372 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -487,4 +487,20 @@ @anim/slide_in_from_bottom @anim/slide_out_to_bottom + + From 8c4e37997bfd7b51f6fc303d98a5fdefccd0ab68 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Tue, 25 Jun 2019 14:51:38 +0300 Subject: [PATCH 016/108] add hints & view delegate for feedback blocks --- app/src/main/assets/fonts/PT-Mono.ttf | Bin 0 -> 180120 bytes .../step_quiz/model/StepQuizFeedbackState.kt | 9 ++ .../StepQuizFeedbackBlocksDelegate.kt | 95 ++++++++++++++++++ .../ui/fragment/StringStepQuizFragment.kt | 17 ++-- ...g_step_quiz_feedback_correct_with_hint.xml | 7 ++ .../bg_step_quiz_feedback_wrong_with_hint.xml | 7 ++ .../main/res/drawable/bg_step_quiz_hint.xml | 7 ++ .../res/drawable/ic_step_quiz_validation.xml | 24 +++++ .../res/layout/fragment_step_quiz_string.xml | 49 ++------- .../layout_step_quiz_feedback_block.xml | 61 +++++++++++ app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 2 - 13 files changed, 227 insertions(+), 55 deletions(-) create mode 100755 app/src/main/assets/fonts/PT-Mono.ttf create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/model/StepQuizFeedbackState.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt create mode 100644 app/src/main/res/drawable/bg_step_quiz_feedback_correct_with_hint.xml create mode 100644 app/src/main/res/drawable/bg_step_quiz_feedback_wrong_with_hint.xml create mode 100644 app/src/main/res/drawable/bg_step_quiz_hint.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_validation.xml create mode 100644 app/src/main/res/layout/layout_step_quiz_feedback_block.xml diff --git a/app/src/main/assets/fonts/PT-Mono.ttf b/app/src/main/assets/fonts/PT-Mono.ttf new file mode 100755 index 0000000000000000000000000000000000000000..547ebcef07cace8862cbb4b8f6b0a2e1ebc49616 GIT binary patch literal 180120 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%0DLoE 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 cf42eqR${LTrCFbZuTRE2wad0*fLe9^e() + + init { + viewStateDelegate.addState() + viewStateDelegate.addState(stepQuizFeedbackEvaluation) + viewStateDelegate.addState(stepQuizFeedbackCorrect, stepQuizFeedbackHint) + viewStateDelegate.addState(stepQuizFeedbackWrong, stepQuizFeedbackHint) + viewStateDelegate.addState(stepQuizFeedbackValidation) + + val drawable = AppCompatResources.getDrawable(context, R.drawable.ic_step_quiz_evaluation) as? AnimationDrawable + stepQuizFeedbackEvaluation.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + drawable?.start() + + stepQuizFeedbackCorrect.setCompoundDrawables(start = R.drawable.ic_step_quiz_correct) + stepQuizFeedbackCorrect.text = resources.getStringArray(R.array.step_quiz_feedback_correct).random() + + stepQuizFeedbackWrong.setCompoundDrawables(start = R.drawable.ic_step_quiz_wrong) + stepQuizFeedbackWrong.setText(R.string.step_quiz_feedback_wrong_not_last_try) + + stepQuizFeedbackValidation.setCompoundDrawables(start = R.drawable.ic_step_quiz_validation) + stepQuizFeedbackValidation.setText(R.string.step_quiz_feedback_validation_fill_blanks) + } + + fun setState(state: StepQuizFeedbackState) { + viewStateDelegate.switchState(state) + when (state) { + is StepQuizFeedbackState.Correct -> { + stepQuizFeedbackCorrect.text = + if (state.isFreeAnswer) { + context.getString(R.string.step_quiz_feedback_correct_free_answer) + } else { + resources.getStringArray(R.array.step_quiz_feedback_correct).random() + } + setHint(stepQuizFeedbackCorrect, R.drawable.bg_step_quiz_feedback_correct, R.drawable.bg_step_quiz_feedback_correct_with_hint, state.hint) + } + + is StepQuizFeedbackState.Wrong -> { + @StringRes + val stringRes = + if (state.isLastTry) { + R.string.step_quiz_feedback_wrong_last_try + } else { + R.string.step_quiz_feedback_wrong_not_last_try + } + stepQuizFeedbackWrong.setText(stringRes) + setHint(stepQuizFeedbackWrong, R.drawable.bg_step_quiz_feedback_wrong, R.drawable.bg_step_quiz_feedback_wrong_with_hint, state.hint) + } + + is StepQuizFeedbackState.Validation -> + stepQuizFeedbackValidation.text = state.message + } + } + + private fun setHint( + targetView: TextView, + @DrawableRes backgroundRes: Int, @DrawableRes hintedBackgroundRes: Int, + hint: String? + ) { + if (hint != null) { + targetView.setBackgroundResource(hintedBackgroundRes) + stepQuizFeedbackHint.text = hint + stepQuizFeedbackHint.visibility = View.VISIBLE + } else { + targetView.setBackgroundResource(backgroundRes) + stepQuizFeedbackHint.visibility = View.GONE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt index 6393a55973..35b7e3c809 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt @@ -1,9 +1,7 @@ package org.stepik.android.view.step_quiz_string.ui.fragment -import android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.support.v4.app.Fragment -import android.support.v7.content.res.AppCompatResources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,9 +9,10 @@ import kotlinx.android.synthetic.main.fragment_step_quiz_string.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper -import org.stepic.droid.ui.util.setCompoundDrawables import org.stepic.droid.util.argument import org.stepik.android.domain.lesson.model.LessonData +import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate class StringStepQuizFragment : Fragment() { companion object { @@ -28,6 +27,8 @@ class StringStepQuizFragment : Fragment() { private var lessonData: LessonData by argument() private var stepWrapper: StepPersistentWrapper by argument() + private lateinit var stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_step_quiz_string, container, false) @@ -37,14 +38,8 @@ class StringStepQuizFragment : Fragment() { stepQuizSubmit.setOnClickListener { } stepQuizSubmit.isEnabled = true - val drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_step_quiz_evaluation) as? AnimationDrawable - stepQuizFeedbackEvaluation.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) - drawable?.start() - - stepQuizFeedbackCorrect.setCompoundDrawables(start = R.drawable.ic_step_quiz_correct) - stepQuizFeedbackCorrect.text = resources.getStringArray(R.array.step_quiz_feedback_correct).random() + stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) - stepQuizFeedbackWrong.setCompoundDrawables(start = R.drawable.ic_step_quiz_wrong) - stepQuizFeedbackWrong.setText(R.string.step_quiz_feedback_wrong_not_last_try) + stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Wrong(hint = "Lorem ipsum dit aleri poel pelmeni")) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_feedback_correct_with_hint.xml b/app/src/main/res/drawable/bg_step_quiz_feedback_correct_with_hint.xml new file mode 100644 index 0000000000..e0cf5bf693 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_feedback_correct_with_hint.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_feedback_wrong_with_hint.xml b/app/src/main/res/drawable/bg_step_quiz_feedback_wrong_with_hint.xml new file mode 100644 index 0000000000..81c6fbf0d5 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_feedback_wrong_with_hint.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_hint.xml b/app/src/main/res/drawable/bg_step_quiz_hint.xml new file mode 100644 index 0000000000..2573164e71 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_hint.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_validation.xml b/app/src/main/res/drawable/ic_step_quiz_validation.xml new file mode 100644 index 0000000000..7bdfdcc56d --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_validation.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_string.xml index 0b49689009..3e15ce4aea 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_string.xml @@ -39,55 +39,20 @@ app:layout_constraintEnd_toEndOf="parent" tools:targetApi="lollipop" /> - - - - - + android:layout_margin="16dp" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2126753607..81a658f745 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -720,6 +720,8 @@ Пока неправильно, попробуйте еще раз! Неверно + Необходимо заполнить все пробелы перед отправкой + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c49ee15add..52a60a0358 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -750,6 +750,8 @@ Wrong. Let\'s try again. Incorrect + All blanks should be filled + Choose email app diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fdc3b8f372..f14b77011f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -500,7 +500,5 @@ fonts/Roboto-Regular.ttf 16sp 16dp - 16dp - 16dp From 8344b1cecc5d7552b56b7374b877940d73faa243 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Tue, 25 Jun 2019 16:19:44 +0300 Subject: [PATCH 017/108] add step quiz interactor --- .../interactor/StepQuizInteractor.kt | 43 +++++++++++++++++++ .../repository/SubmissionRepository.kt | 3 ++ .../ui/factory/StepQuizFragmentFactoryImpl.kt | 4 +- .../ui/fragment/TextStepQuizFragment.kt} | 10 ++--- ... => color_step_quiz_text_field_stroke.xml} | 0 ..._field.xml => bg_step_quiz_text_field.xml} | 4 +- ...string.xml => fragment_step_quiz_text.xml} | 4 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/dimens.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 10 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt rename app/src/main/java/org/stepik/android/view/{step_quiz_string/ui/fragment/StringStepQuizFragment.kt => step_quiz_text/ui/fragment/TextStepQuizFragment.kt} (84%) rename app/src/main/res/color/{color_step_quiz_string_field_stroke.xml => color_step_quiz_text_field_stroke.xml} (100%) rename app/src/main/res/drawable/{bg_step_quiz_string_field.xml => bg_step_quiz_text_field.xml} (72%) rename app/src/main/res/layout/{fragment_step_quiz_string.xml => fragment_step_quiz_text.xml} (95%) diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt new file mode 100644 index 0000000000..b20d007748 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt @@ -0,0 +1,43 @@ +package org.stepik.android.domain.step_quiz.interactor + +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import org.stepic.droid.util.maybeFirst +import org.stepik.android.domain.attempt.repository.AttemptRepository +import org.stepik.android.domain.submission.repository.SubmissionRepository +import org.stepik.android.model.Reply +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class StepQuizInteractor +@Inject +constructor( + private val attemptRepository: AttemptRepository, + private val submissionRepository: SubmissionRepository +) { + fun getAttempt(stepId: Long): Single = + attemptRepository + .getAttemptsForStep(stepId) + .maybeFirst() + .filter { it.status == "active" } + .switchIfEmpty(attemptRepository.createAttemptForStep(stepId)) + + fun getSubmission(attemptId: Long): Maybe = + submissionRepository + .getSubmissionsForAttempt(attemptId) + .maybeFirst() + + fun createSubmission(attemptId: Long, reply: Reply): Single = + submissionRepository + .createSubmission(Submission(attempt = attemptId, reply = reply)) + .flatMapObservable { + Observable + .interval(1, TimeUnit.SECONDS) + .flatMapMaybe { submissionRepository.getSubmissionsForAttempt(attemptId).maybeFirst() } + .skipWhile { it.status == Submission.Status.EVALUATION } + } + .firstOrError() +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt index a38726cff6..0623179845 100644 --- a/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt +++ b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt @@ -5,6 +5,9 @@ import org.stepik.android.model.Submission interface SubmissionRepository { fun createSubmission(submission: Submission): Single + + fun getSubmission(submissionId: Long): Single + fun getSubmissionsForAttempt(attemptId: Long): Single> fun getSubmissionsForStep(stepId: Long): Single> } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index 39f43ad6f5..bc04c02520 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -3,7 +3,7 @@ package org.stepik.android.view.step_quiz.ui.factory import android.support.v4.app.Fragment import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants -import org.stepik.android.view.step_quiz_string.ui.fragment.StringStepQuizFragment +import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment import javax.inject.Inject class StepQuizFragmentFactoryImpl @@ -12,7 +12,7 @@ constructor() : StepQuizFragmentFactory { override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment = when (stepPersistentWrapper.step.block?.name) { AppConstants.TYPE_STRING -> - StringStepQuizFragment.newInstance(stepPersistentWrapper) + TextStepQuizFragment.newInstance(stepPersistentWrapper) else -> Fragment() diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt similarity index 84% rename from app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt rename to app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 35b7e3c809..1aa90070cf 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_string/ui/fragment/StringStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -1,11 +1,11 @@ -package org.stepik.android.view.step_quiz_string.ui.fragment +package org.stepik.android.view.step_quiz_text.ui.fragment import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import kotlinx.android.synthetic.main.fragment_step_quiz_string.* +import kotlinx.android.synthetic.main.fragment_step_quiz_text.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper @@ -14,10 +14,10 @@ import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate -class StringStepQuizFragment : Fragment() { +class TextStepQuizFragment : Fragment() { companion object { fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = - StringStepQuizFragment() + TextStepQuizFragment() .apply { // this.lessonData = lessonData this.stepWrapper = stepPersistentWrapper @@ -30,7 +30,7 @@ class StringStepQuizFragment : Fragment() { private lateinit var stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = - inflater.inflate(R.layout.fragment_step_quiz_string, container, false) + inflater.inflate(R.layout.fragment_step_quiz_text, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/res/color/color_step_quiz_string_field_stroke.xml b/app/src/main/res/color/color_step_quiz_text_field_stroke.xml similarity index 100% rename from app/src/main/res/color/color_step_quiz_string_field_stroke.xml rename to app/src/main/res/color/color_step_quiz_text_field_stroke.xml diff --git a/app/src/main/res/drawable/bg_step_quiz_string_field.xml b/app/src/main/res/drawable/bg_step_quiz_text_field.xml similarity index 72% rename from app/src/main/res/drawable/bg_step_quiz_string_field.xml rename to app/src/main/res/drawable/bg_step_quiz_text_field.xml index fbc5d64308..8d70da488e 100644 --- a/app/src/main/res/drawable/bg_step_quiz_string_field.xml +++ b/app/src/main/res/drawable/bg_step_quiz_text_field.xml @@ -2,7 +2,7 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_string.xml b/app/src/main/res/layout/fragment_step_quiz_text.xml similarity index 95% rename from app/src/main/res/layout/fragment_step_quiz_string.xml rename to app/src/main/res/layout/fragment_step_quiz_text.xml index 3e15ce4aea..9bca71d3e1 100644 --- a/app/src/main/res/layout/fragment_step_quiz_string.xml +++ b/app/src/main/res/layout/fragment_step_quiz_text.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:minHeight="48dp" - android:background="@drawable/bg_step_quiz_string_field" + android:background="@drawable/bg_step_quiz_text_field" fontPath="fonts/Roboto-Medium.ttf" android:textSize="16sp" @@ -20,7 +20,7 @@ android:letterSpacing="0.01" android:lineSpacingExtra="6sp" - android:hint="@string/step_quiz_string_field_hint" + android:hint="@string/step_quiz_text_field_hint" android:textColorHint="@color/new_accent_color_alpha_40" android:gravity="center_vertical" diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 81a658f745..f03c1f05ad 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -713,7 +713,7 @@ Отправить Попробовать снова - Введите ответ… + Введите ответ… Проверка… Любой ответ будет оценен как правильный diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 09a602140d..ee8f3a4f21 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -220,5 +220,5 @@ @dimen/step_control_block_radius 48dp - 8dp + 8dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52a60a0358..d8b0ad14bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -743,7 +743,7 @@ Submit Try again - Type your answer… + Type your answer… Evaluation… Any text response will be graded as correct From e5efef803fe59ea09114749ec285e5e96ab82a74 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 25 Jun 2019 17:56:15 +0300 Subject: [PATCH 018/108] add support suffix to dependencies --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index a919e65857..4720a9cdef 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -175,8 +175,8 @@ ext.libraries = [ shortcutBadger : "me.leolin:ShortcutBadger:$versions.shortcutBadger", StoriesKit : "ru.nobird.android:StoriesKitSupport:$versions.StoriesKit", - AdapterDelegates : "ru.nobird.android.ui:AdapterDelegates:$versions.AdapterDelegates", - Adapters : "ru.nobird.android.ui:Adapters:$versions.Adapters", + AdapterDelegates : "ru.nobird.android.ui:AdapterDelegatesSupport:$versions.AdapterDelegates", + Adapters : "ru.nobird.android.ui:AdaptersSupport:$versions.Adapters", // Developer Tools leakCanary : "com.squareup.leakcanary:leakcanary-android:$versions.leakCanary", From b88d163f4cbc7d894188f9424da0538cc8ca5162 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 25 Jun 2019 19:02:43 +0300 Subject: [PATCH 019/108] choices adapter delegate and item view --- .../ui/adapter/ChoicesAdapterDelegate.kt | 35 +++++++++++++++++++ app/src/main/res/layout/item_choice_quiz.xml | 20 +++++++++++ 2 files changed, 55 insertions(+) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt create mode 100644 app/src/main/res/layout/item_choice_quiz.xml diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt new file mode 100644 index 0000000000..82d1aa4d29 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -0,0 +1,35 @@ +package org.stepik.android.view.step_quiz_choice.ui.adapter + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import org.stepic.droid.R +import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate +import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder +import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper + +class ChoicesAdapterDelegate( + private val selectionHelper: SelectionHelper, + private val onClick: (String) -> Unit +): AdapterDelegate>() { + override fun isForViewType(position: Int, data: String): Boolean = + true + + override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = + ViewHolder(createView(parent, R.layout.item_choice_quiz)) + + inner class ViewHolder( + containerView: View + ) : DelegateViewHolder(containerView) { + + init { + containerView.setOnClickListener { onClick(itemData as String) } + } + + override fun onBind(data: String) { + val choiceText = itemView.findViewById(R.id.item_choice_text) as TextView + itemView.isSelected = selectionHelper.isSelected(adapterPosition) + choiceText.text = data + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml new file mode 100644 index 0000000000..aa19256fc4 --- /dev/null +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file From 1c3854645204154e1bbd7090cd78220e1c6157de Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 25 Jun 2019 19:39:56 +0300 Subject: [PATCH 020/108] quiz form delegate --- .../ui/delegate/ChoiceQuizFormDelegate.kt | 48 +++++++++++++++++++ .../ui/delegate/StepQuizFormDelegate.kt | 12 +++++ 2 files changed, 60 insertions(+) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt new file mode 100644 index 0000000000..509299be18 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -0,0 +1,48 @@ +package org.stepik.android.view.step_quiz_choice.ui.delegate + +import android.support.v7.widget.LinearLayoutManager +import android.view.View +import kotlinx.android.synthetic.main.view_choice_quiz_attempt.view.* +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt +import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate +import ru.nobird.android.ui.adapterssupport.DefaultDelegateAdapter +import ru.nobird.android.ui.adapterssupport.selection.MultipleChoiceSelectionHelper +import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelper + +class ChoiceQuizFormDelegate( + private val choiceAttemptView: View +) : StepQuizFormDelegate() { + private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() + + init { + choiceAttemptView.choices_recycler.apply { + adapter = choicesAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + } + } + + override var isEnabled: Boolean = false + set(value) { + field = value + choiceAttemptView.isEnabled = value + } + + override fun setAttempt(attempt: Attempt?) { + val dataSet = attempt?.dataset + dataSet?.options?.let { options -> + choicesAdapter.items = options + val selectionHelper = if (dataSet.isMultipleChoice) { + SingleChoiceSelectionHelper(choicesAdapter) + } else { + MultipleChoiceSelectionHelper(choicesAdapter) + } + choicesAdapter += ChoicesAdapterDelegate(selectionHelper) { selectionHelper.select(choicesAdapter.items.indexOf(it)) } + } + + } + + override fun setSubmission(submission: Submission?) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt new file mode 100644 index 0000000000..a17b8a0d6d --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt @@ -0,0 +1,12 @@ +package org.stepik.android.view.step_quiz_choice.ui.delegate + +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt + +// TODO Temporary placed in this package, will move later +abstract class StepQuizFormDelegate { + abstract var isEnabled: Boolean + + abstract fun setAttempt(attempt: Attempt?) + abstract fun setSubmission(submission: Submission?) +} \ No newline at end of file From 986184467ca2b752d7c58db68f0c4adc5a903985 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 25 Jun 2019 19:40:22 +0300 Subject: [PATCH 021/108] choice quiz attempt layout --- .../main/res/layout/view_choice_quiz_attempt.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/src/main/res/layout/view_choice_quiz_attempt.xml diff --git a/app/src/main/res/layout/view_choice_quiz_attempt.xml b/app/src/main/res/layout/view_choice_quiz_attempt.xml new file mode 100644 index 0000000000..2738a61cb3 --- /dev/null +++ b/app/src/main/res/layout/view_choice_quiz_attempt.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file From bbf1046de92ad078c95a147ec232477b43fec0ff Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 25 Jun 2019 19:40:47 +0300 Subject: [PATCH 022/108] choice step quiz fragment updated with delegate --- .../ui/fragment/ChoiceStepQuizFragment.kt | 19 ++++++++++++ .../res/layout/fragment_step_quiz_choice.xml | 30 ++++--------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index 49439c4d1c..5130430f8d 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -5,9 +5,14 @@ import android.support.v4.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_step_quiz_choice.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument +import org.stepik.android.model.attempts.Attempt +import org.stepik.android.model.attempts.Dataset +import org.stepik.android.model.attempts.DatasetWrapper +import org.stepik.android.view.step_quiz_choice.ui.delegate.ChoiceQuizFormDelegate class ChoiceStepQuizFragment: Fragment() { companion object { @@ -19,7 +24,21 @@ class ChoiceStepQuizFragment: Fragment() { } private var stepWrapper: StepPersistentWrapper by argument() + private lateinit var choiceQuizFormDelegate: ChoiceQuizFormDelegate override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_step_quiz_choice, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + choiceQuizFormDelegate = ChoiceQuizFormDelegate(choice_quiz_attempt) + choiceQuizFormDelegate.setAttempt(Attempt( + _dataset = DatasetWrapper( + Dataset( + options = listOf("Variant 1", "Variant 2", "Variant 3\nExtra line", "Variant 4"), + isMultipleChoice = false + ) + ) + )) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_step_quiz_choice.xml b/app/src/main/res/layout/fragment_step_quiz_choice.xml index bc6b6e8b47..3d29a6f33c 100644 --- a/app/src/main/res/layout/fragment_step_quiz_choice.xml +++ b/app/src/main/res/layout/fragment_step_quiz_choice.xml @@ -1,26 +1,8 @@ - - - - - \ No newline at end of file + android:layout_height="wrap_content"> + + \ No newline at end of file From ab296f9656078d746134eda7da23602df4d2e48d Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 11:48:55 +0300 Subject: [PATCH 023/108] highlighting chosen and not chosen options --- .../ui/adapter/ChoicesAdapterDelegate.kt | 15 +++++++++++-- app/src/main/res/layout/item_choice_quiz.xml | 21 ++++++------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 82d1aa4d29..4856f11a53 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -1,9 +1,12 @@ package org.stepik.android.view.step_quiz_choice.ui.adapter +import android.graphics.drawable.GradientDrawable +import android.support.v4.content.ContextCompat import android.view.View import android.view.ViewGroup import android.widget.TextView import org.stepic.droid.R +import org.stepic.droid.util.DpPixelsHelper import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper @@ -27,9 +30,17 @@ class ChoicesAdapterDelegate( } override fun onBind(data: String) { - val choiceText = itemView.findViewById(R.id.item_choice_text) as TextView + itemView as TextView itemView.isSelected = selectionHelper.isSelected(adapterPosition) - choiceText.text = data + itemView.text = data + val shape = itemView.background as GradientDrawable + if (itemView.isSelected) { + shape.setColor(ContextCompat.getColor(itemView.context, R.color.choice_checked)) + shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, R.color.choice_checked_border)) + } else { + shape.setColor(ContextCompat.getColor(itemView.context, R.color.choice_not_checked)) + shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, R.color.choice_not_checked_border)) + } } } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index aa19256fc4..70bdf3ed27 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -1,20 +1,11 @@ - - - - \ No newline at end of file + android:background="@drawable/choice_not_checked_background" + android:padding="14dp" + android:textColor="@color/black" + tools:text="Hello there bla bla" /> \ No newline at end of file From fa933f04a83dd0e13c60c09a07c99acce6cbd468 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 11:49:39 +0300 Subject: [PATCH 024/108] implement choice quiz form delegate --- .../ui/delegate/ChoiceQuizFormDelegate.kt | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 509299be18..93534b4766 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -3,17 +3,20 @@ package org.stepik.android.view.step_quiz_choice.ui.delegate import android.support.v7.widget.LinearLayoutManager import android.view.View import kotlinx.android.synthetic.main.view_choice_quiz_attempt.view.* +import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate import ru.nobird.android.ui.adapterssupport.DefaultDelegateAdapter import ru.nobird.android.ui.adapterssupport.selection.MultipleChoiceSelectionHelper +import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelper class ChoiceQuizFormDelegate( private val choiceAttemptView: View ) : StepQuizFormDelegate() { private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() + private lateinit var selectionHelper: SelectionHelper init { choiceAttemptView.choices_recycler.apply { @@ -22,27 +25,53 @@ class ChoiceQuizFormDelegate( } } - override var isEnabled: Boolean = false - set(value) { - field = value - choiceAttemptView.isEnabled = value - } + override var isEnabled: Boolean = true override fun setAttempt(attempt: Attempt?) { val dataSet = attempt?.dataset dataSet?.options?.let { options -> choicesAdapter.items = options - val selectionHelper = if (dataSet.isMultipleChoice) { - SingleChoiceSelectionHelper(choicesAdapter) - } else { + selectionHelper = if (dataSet.isMultipleChoice) { MultipleChoiceSelectionHelper(choicesAdapter) + } else { + SingleChoiceSelectionHelper(choicesAdapter) } - choicesAdapter += ChoicesAdapterDelegate(selectionHelper) { selectionHelper.select(choicesAdapter.items.indexOf(it)) } + choicesAdapter += ChoicesAdapterDelegate(selectionHelper, onClick = ::handleChoiceClick) } - } override fun setSubmission(submission: Submission?) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + submission?.reply?.choices?.let { setChoices(it)} } + + private fun handleChoiceClick(choice: String) { + if (!isEnabled) return + when (selectionHelper) { + is SingleChoiceSelectionHelper -> { + selectionHelper.reset() + selectionHelper.select(choicesAdapter.items.indexOf(choice)) + choicesAdapter.notifyDataSetChanged() + } + is MultipleChoiceSelectionHelper -> { + selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) + } + } + } + + private fun setChoices(choices: List) { + (0 until choices.size).forEach {pos -> + if (choices[pos]) { + selectionHelper.select(pos) + } + } + } + + val reply: Reply + get() { + val selection = (0 until choicesAdapter.itemCount) + .map { + selectionHelper.isSelected(it) + } + return Reply(choices = selection) + } } \ No newline at end of file From a5edd2eb94c85fdd16bf553c9f9641b59463933a Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 13:05:29 +0300 Subject: [PATCH 025/108] changed variant correct icon size --- app/src/main/res/drawable/ic_correct_checkmark.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/drawable/ic_correct_checkmark.xml b/app/src/main/res/drawable/ic_correct_checkmark.xml index 71eda325eb..e594fbe84d 100644 --- a/app/src/main/res/drawable/ic_correct_checkmark.xml +++ b/app/src/main/res/drawable/ic_correct_checkmark.xml @@ -1,5 +1,5 @@ - + Date: Wed, 26 Jun 2019 13:36:53 +0300 Subject: [PATCH 026/108] add step quiz form delegate --- .../repository/SubmissionRepository.kt | 3 - .../step_quiz_text/TextStepQuizPresenter.kt | 62 +++++++++++++++++++ .../step_quiz_text/TextStepQuizView.kt | 17 +++++ .../view/injection/step/StepComponent.kt | 12 +++- .../step_quiz_text/TextStepQuizModule.kt | 19 ++++++ .../mapper/StepQuizFeedbackMapper.kt | 26 ++++++++ .../ui/delegate/StepQuizFormDelegate.kt | 26 ++++++++ .../ui/fragment/TextStepQuizFragment.kt | 33 +++++++++- 8 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt create mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt diff --git a/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt index 0623179845..a38726cff6 100644 --- a/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt +++ b/app/src/main/java/org/stepik/android/domain/submission/repository/SubmissionRepository.kt @@ -5,9 +5,6 @@ import org.stepik.android.model.Submission interface SubmissionRepository { fun createSubmission(submission: Submission): Single - - fun getSubmission(submissionId: Long): Single - fun getSubmissionsForAttempt(attemptId: Long): Single> fun getSubmissionsForStep(stepId: Long): Single> } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt new file mode 100644 index 0000000000..38312722ab --- /dev/null +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt @@ -0,0 +1,62 @@ +package org.stepik.android.presentation.step_quiz_text + +import io.reactivex.Scheduler +import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.subscribeBy +import org.stepic.droid.di.qualifiers.BackgroundScheduler +import org.stepic.droid.di.qualifiers.MainScheduler +import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepik.android.domain.step_quiz.interactor.StepQuizInteractor +import org.stepik.android.model.Reply +import org.stepik.android.presentation.base.PresenterBase +import javax.inject.Inject + +class TextStepQuizPresenter +@Inject +constructor( + private val stepQuizInteractor: StepQuizInteractor, + + @BackgroundScheduler + private val backgroundScheduler: Scheduler, + @MainScheduler + private val mainScheduler: Scheduler +) : PresenterBase() { + private var state: TextStepQuizView.State = TextStepQuizView.State.Idle + set(value) { + field = value + view?.setState(value) + } + + override fun attachView(view: TextStepQuizView) { + super.attachView(view) + view.setState(state) + } + + fun onStepData(stepId: Long) { + if (state == TextStepQuizView.State.Idle) { + fetchAttempt(stepId) + } + } + + private fun fetchAttempt(stepId: Long) { + state = TextStepQuizView.State.Loading + compositeDisposable += stepQuizInteractor + .getAttempt(stepId) + .flatMap { attempt -> + stepQuizInteractor + .getSubmission(attempt.id) + .map { TextStepQuizView.State.SubmissionLoaded(attempt, it) as TextStepQuizView.State } + .toSingle(TextStepQuizView.State.AttemptLoaded(attempt)) + } + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { state = it }, + onError = { state = TextStepQuizView.State.NetworkError } + ) + } + + fun createSubmission(reply: Reply) { +// val attemptId + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt new file mode 100644 index 0000000000..af1977feab --- /dev/null +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt @@ -0,0 +1,17 @@ +package org.stepik.android.presentation.step_quiz_text + +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt + +interface TextStepQuizView { + sealed class State { + object Idle : State() + object Loading : State() + data class AttemptLoaded(val attempt: Attempt) : State() + data class SubmissionLoaded(val attempt: Attempt, val submission: Submission) : State() + + object NetworkError : State() + } + + fun setState(state: State) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt index 78032269e3..2f44cff3f9 100644 --- a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt +++ b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt @@ -1,14 +1,22 @@ package org.stepik.android.view.injection.step import dagger.Subcomponent +import org.stepik.android.view.injection.attempt.AttemptDataModule import org.stepik.android.view.injection.step_content.StepContentModule import org.stepik.android.view.injection.step_quiz.StepQuizModule +import org.stepik.android.view.injection.step_quiz_text.TextStepQuizModule +import org.stepik.android.view.injection.submission.SubmissionDataModule import org.stepik.android.view.step.ui.fragment.StepFragment +import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment @Subcomponent(modules = [ StepModule::class, StepContentModule::class, - StepQuizModule::class + StepQuizModule::class, + + TextStepQuizModule::class, + AttemptDataModule::class, + SubmissionDataModule::class ]) interface StepComponent { @Subcomponent.Builder @@ -17,4 +25,6 @@ interface StepComponent { } fun inject(stepFragment: StepFragment) + + fun inject(textStepQuizFragment: TextStepQuizFragment) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt new file mode 100644 index 0000000000..68bd6dd390 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt @@ -0,0 +1,19 @@ +package org.stepik.android.view.injection.step_quiz_text + +import android.arch.lifecycle.ViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import org.stepik.android.presentation.base.injection.ViewModelKey +import org.stepik.android.presentation.step_quiz_text.TextStepQuizPresenter + +@Module +abstract class TextStepQuizModule { + /** + * Presentation + */ + @Binds + @IntoMap + @ViewModelKey(TextStepQuizPresenter::class) + internal abstract fun bindTextStepQuizPresenter(textStepQuizPresenter: TextStepQuizPresenter): ViewModel +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt new file mode 100644 index 0000000000..27c8a2ad0b --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt @@ -0,0 +1,26 @@ +package org.stepik.android.view.step_quiz.mapper + +import org.stepik.android.model.Submission +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState + +class StepQuizFeedbackMapper { + fun mapToStepQuizFeedbackState(state: TextStepQuizView.State): StepQuizFeedbackState = + if (state is TextStepQuizView.State.SubmissionLoaded) { + when (state.submission.status) { + Submission.Status.CORRECT -> + StepQuizFeedbackState.Correct(state.submission.hint) + + Submission.Status.WRONG -> + StepQuizFeedbackState.Wrong(state.submission.hint) + + Submission.Status.EVALUATION -> + StepQuizFeedbackState.Evaluation + + else -> + StepQuizFeedbackState.Idle + } + } else { + StepQuizFeedbackState.Idle + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt new file mode 100644 index 0000000000..224ed3e4df --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt @@ -0,0 +1,26 @@ +package org.stepik.android.view.step_quiz.ui.delegate + +import org.stepik.android.model.Reply +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt + +interface StepQuizFormDelegate { + /** + * If [isEnabled] == false form should block any changes from user + */ + var isEnabled: Boolean + + fun setAttempt(attempt: Attempt) + fun setSubmission(submission: Submission) + + /** + * Generates reply from current form data + */ + fun createReply(): Reply + + /** + * Validates form for ability to create a reply + * @returns null if validation successful or message string otherwise + */ + fun validateForm(): String? +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 1aa90070cf..abfdbb4eb4 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -1,5 +1,7 @@ package org.stepik.android.view.step_quiz_text.ui.fragment +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v4.app.Fragment import android.view.LayoutInflater @@ -8,27 +10,50 @@ import android.view.ViewGroup import kotlinx.android.synthetic.main.fragment_step_quiz_text.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R +import org.stepic.droid.base.App import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument import org.stepik.android.domain.lesson.model.LessonData +import org.stepik.android.presentation.step_quiz_text.TextStepQuizPresenter +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate +import javax.inject.Inject -class TextStepQuizFragment : Fragment() { +class TextStepQuizFragment : Fragment(), TextStepQuizView { companion object { fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = TextStepQuizFragment() .apply { -// this.lessonData = lessonData this.stepWrapper = stepPersistentWrapper } } + @Inject + internal lateinit var viewModelFactory: ViewModelProvider.Factory + + private lateinit var presenter: TextStepQuizPresenter + private var lessonData: LessonData by argument() private var stepWrapper: StepPersistentWrapper by argument() private lateinit var stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + + presenter = ViewModelProviders.of(this, viewModelFactory).get(TextStepQuizPresenter::class.java) + presenter.onStepData(stepWrapper.step.id) + } + + private fun injectComponent() { + App.component() + .stepComponentBuilder() + .build() + .inject(this) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_step_quiz_text, container, false) @@ -42,4 +67,8 @@ class TextStepQuizFragment : Fragment() { stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Wrong(hint = "Lorem ipsum dit aleri poel pelmeni")) } + + override fun setState(state: TextStepQuizView.State) { + + } } \ No newline at end of file From f3736d946df61d631555d037ea3932bd3df1b086 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 13:38:09 +0300 Subject: [PATCH 027/108] updated choice adapter to support new model and correct/incorrect backgrounds --- .../step_quiz_choice/model/Choice.kt | 6 +++ .../step_quiz_choice/model/ChoiceColor.kt | 16 +++++++ .../ui/adapter/ChoicesAdapterDelegate.kt | 48 ++++++++++++++----- 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt create mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt new file mode 100644 index 0000000000..3006b0e049 --- /dev/null +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt @@ -0,0 +1,6 @@ +package org.stepik.android.presentation.step_quiz_choice.model + +data class Choice( + val option: String, + var correct: Boolean? = null +) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt new file mode 100644 index 0000000000..749fabf69b --- /dev/null +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt @@ -0,0 +1,16 @@ +package org.stepik.android.presentation.step_quiz_choice.model + +import android.support.annotation.ColorRes +import org.stepic.droid.R + +enum class ChoiceColor( + @ColorRes + val backgroundColor: Int, + @ColorRes + val strokeColor: Int +) { + CHECKED(R.color.choice_checked, R.color.choice_checked_border), + NOT_CHECKED(R.color.choice_not_checked, R.color.choice_not_checked_border), + CORRECT(R.color.choice_correct, R.color.choice_correct_border), + INCORRECT(R.color.choice_incorrect, R.color.choice_incorrect_border) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 4856f11a53..5442590750 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -7,40 +7,62 @@ import android.view.ViewGroup import android.widget.TextView import org.stepic.droid.R import org.stepic.droid.util.DpPixelsHelper +import org.stepik.android.presentation.step_quiz_choice.model.Choice +import org.stepik.android.presentation.step_quiz_choice.model.ChoiceColor import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper class ChoicesAdapterDelegate( private val selectionHelper: SelectionHelper, - private val onClick: (String) -> Unit -): AdapterDelegate>() { - override fun isForViewType(position: Int, data: String): Boolean = + private val onClick: (Choice) -> Unit +): AdapterDelegate>() { + override fun isForViewType(position: Int, data: Choice): Boolean = true - override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = + override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_choice_quiz)) inner class ViewHolder( containerView: View - ) : DelegateViewHolder(containerView) { + ) : DelegateViewHolder(containerView) { init { - containerView.setOnClickListener { onClick(itemData as String) } + containerView.setOnClickListener { onClick(itemData as Choice) } } - override fun onBind(data: String) { + override fun onBind(data: Choice) { itemView as TextView itemView.isSelected = selectionHelper.isSelected(adapterPosition) - itemView.text = data + itemView.text = data.option + itemView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + bindColor(data) + } + + private fun bindColor(data: Choice) { val shape = itemView.background as GradientDrawable + val choiceColor = inferChoiceColor(data) + shape.setColor(ContextCompat.getColor(itemView.context, choiceColor.backgroundColor)) + shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, choiceColor.strokeColor)) + } + + private fun inferChoiceColor(data: Choice): ChoiceColor = if (itemView.isSelected) { - shape.setColor(ContextCompat.getColor(itemView.context, R.color.choice_checked)) - shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, R.color.choice_checked_border)) + when (data.correct) { + true -> { + itemView as TextView + itemView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_correct_checkmark, 0) + ChoiceColor.CORRECT + } + false -> { + ChoiceColor.INCORRECT + } + else -> { + ChoiceColor.CHECKED + } + } } else { - shape.setColor(ContextCompat.getColor(itemView.context, R.color.choice_not_checked)) - shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, R.color.choice_not_checked_border)) + ChoiceColor.NOT_CHECKED } - } } } \ No newline at end of file From 8a71a57f382059191e373e55648110863271abf3 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 13:51:32 +0300 Subject: [PATCH 028/108] moved StepQuizFormDelegate --- .../ui/delegate/StepQuizFormDelegate.kt | 26 +++++++++++++++++++ .../ui/delegate/StepQuizFormDelegate.kt | 12 --------- 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt delete mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt new file mode 100644 index 0000000000..224ed3e4df --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt @@ -0,0 +1,26 @@ +package org.stepik.android.view.step_quiz.ui.delegate + +import org.stepik.android.model.Reply +import org.stepik.android.model.Submission +import org.stepik.android.model.attempts.Attempt + +interface StepQuizFormDelegate { + /** + * If [isEnabled] == false form should block any changes from user + */ + var isEnabled: Boolean + + fun setAttempt(attempt: Attempt) + fun setSubmission(submission: Submission) + + /** + * Generates reply from current form data + */ + fun createReply(): Reply + + /** + * Validates form for ability to create a reply + * @returns null if validation successful or message string otherwise + */ + fun validateForm(): String? +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt deleted file mode 100644 index a17b8a0d6d..0000000000 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/StepQuizFormDelegate.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.stepik.android.view.step_quiz_choice.ui.delegate - -import org.stepik.android.model.Submission -import org.stepik.android.model.attempts.Attempt - -// TODO Temporary placed in this package, will move later -abstract class StepQuizFormDelegate { - abstract var isEnabled: Boolean - - abstract fun setAttempt(attempt: Attempt?) - abstract fun setSubmission(submission: Submission?) -} \ No newline at end of file From acd151029309710673ef170a3d793d1710587463 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 18:48:27 +0300 Subject: [PATCH 029/108] removed redundant backgrounds --- .../main/res/drawable/choice_checked_background.xml | 13 ------------- .../main/res/drawable/choice_correct_background.xml | 13 ------------- .../res/drawable/choice_incorrect_background.xml | 13 ------------- 3 files changed, 39 deletions(-) delete mode 100644 app/src/main/res/drawable/choice_checked_background.xml delete mode 100644 app/src/main/res/drawable/choice_correct_background.xml delete mode 100644 app/src/main/res/drawable/choice_incorrect_background.xml diff --git a/app/src/main/res/drawable/choice_checked_background.xml b/app/src/main/res/drawable/choice_checked_background.xml deleted file mode 100644 index 03322a66c5..0000000000 --- a/app/src/main/res/drawable/choice_checked_background.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/choice_correct_background.xml b/app/src/main/res/drawable/choice_correct_background.xml deleted file mode 100644 index 8b4f955048..0000000000 --- a/app/src/main/res/drawable/choice_correct_background.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/choice_incorrect_background.xml b/app/src/main/res/drawable/choice_incorrect_background.xml deleted file mode 100644 index 58a9456e29..0000000000 --- a/app/src/main/res/drawable/choice_incorrect_background.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file From bcdd9be5a54159efa1d1c98bcdbeb2a2b1fb17b2 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 18:49:08 +0300 Subject: [PATCH 030/108] reworked item choice backgrounds --- ...checked_background.xml => bg_choice_quiz_selected.xml} | 2 +- .../res/drawable/bg_compound_step_quiz_feedback_tip.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) rename app/src/main/res/drawable/{choice_not_checked_background.xml => bg_choice_quiz_selected.xml} (79%) create mode 100644 app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml diff --git a/app/src/main/res/drawable/choice_not_checked_background.xml b/app/src/main/res/drawable/bg_choice_quiz_selected.xml similarity index 79% rename from app/src/main/res/drawable/choice_not_checked_background.xml rename to app/src/main/res/drawable/bg_choice_quiz_selected.xml index 325bb72d4c..7a7a5a488f 100644 --- a/app/src/main/res/drawable/choice_not_checked_background.xml +++ b/app/src/main/res/drawable/bg_choice_quiz_selected.xml @@ -1,5 +1,5 @@ - + + + + + \ No newline at end of file From 03b2db004803920627d6d288900f4ceb4a57c534 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 18:52:31 +0300 Subject: [PATCH 031/108] decomposed background binding in ChoicesAdapterDelegate --- .../droid/util/GradientDrawableExtensions.kt | 13 +++++ .../step_quiz_choice/model/Choice.kt | 3 +- .../ui/adapter/ChoicesAdapterDelegate.kt | 56 ++++++++++++++----- app/src/main/res/layout/item_choice_quiz.xml | 31 +++++++--- app/src/main/res/values/dimens.xml | 3 + 5 files changed, 85 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt diff --git a/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt b/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt new file mode 100644 index 0000000000..d47b94eaa5 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt @@ -0,0 +1,13 @@ +package org.stepic.droid.util + +import android.graphics.drawable.GradientDrawable + +fun GradientDrawable.setTopRoundedCorners(radius: Float) { + val radiiArray = FloatArray(8) + (0 until 4).forEach { radiiArray[it] = radius } + this.cornerRadii = radiiArray +} + +fun GradientDrawable.setRoundedCorners(radius: Float) { + this.cornerRadii = FloatArray(8) { radius } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt index 3006b0e049..f16bf20a63 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt @@ -2,5 +2,6 @@ package org.stepik.android.presentation.step_quiz_choice.model data class Choice( val option: String, - var correct: Boolean? = null + var correct: Boolean? = null, + var tip: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 5442590750..1cfe90fb89 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -4,9 +4,11 @@ import android.graphics.drawable.GradientDrawable import android.support.v4.content.ContextCompat import android.view.View import android.view.ViewGroup -import android.widget.TextView +import kotlinx.android.synthetic.main.item_choice_quiz.view.* import org.stepic.droid.R import org.stepic.droid.util.DpPixelsHelper +import org.stepic.droid.util.setRoundedCorners +import org.stepic.droid.util.setTopRoundedCorners import org.stepik.android.presentation.step_quiz_choice.model.Choice import org.stepik.android.presentation.step_quiz_choice.model.ChoiceColor import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate @@ -24,34 +26,62 @@ class ChoicesAdapterDelegate( ViewHolder(createView(parent, R.layout.item_choice_quiz)) inner class ViewHolder( - containerView: View - ) : DelegateViewHolder(containerView) { + root: View + ) : DelegateViewHolder(root) { + + private val itemChoiceText = root.itemChoiceText + private val itemChoiceTip = root.itemChoiceTip init { - containerView.setOnClickListener { onClick(itemData as Choice) } + root.setOnClickListener { onClick(itemData as Choice) } } override fun onBind(data: Choice) { - itemView as TextView itemView.isSelected = selectionHelper.isSelected(adapterPosition) - itemView.text = data.option - itemView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) - bindColor(data) + itemChoiceText.apply { + text = data.option + setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } + bindBackground(data) } - private fun bindColor(data: Choice) { - val shape = itemView.background as GradientDrawable + private fun bindBackground(data: Choice) { + val shape = itemChoiceText.background as GradientDrawable val choiceColor = inferChoiceColor(data) + bindTip(data) + setChoiceCorners(shape, data.tip != null) shape.setColor(ContextCompat.getColor(itemView.context, choiceColor.backgroundColor)) - shape.setStroke(DpPixelsHelper.convertDpToPixel(1f).toInt(), ContextCompat.getColor(itemView.context, choiceColor.strokeColor)) + shape.setStroke( + DpPixelsHelper.convertDpToPixel(itemView.context.resources.getDimension(R.dimen.choice_option_stroke_width)).toInt(), + ContextCompat.getColor(itemView.context, choiceColor.strokeColor) + ) + } + + private fun setChoiceCorners(shape: GradientDrawable, hasTip: Boolean) { + if (hasTip) { + shape.setTopRoundedCorners(DpPixelsHelper.convertDpToPixel(8f)) + } else { + shape.setRoundedCorners(DpPixelsHelper.convertDpToPixel(8f)) + } + } + + private fun bindTip(data: Choice) { + if (data.tip == null) { + itemChoiceTip.visibility = View.GONE + + } else { + itemChoiceTip.apply { + visibility = View.VISIBLE + text = data.tip + } + } } private fun inferChoiceColor(data: Choice): ChoiceColor = if (itemView.isSelected) { when (data.correct) { true -> { - itemView as TextView - itemView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_correct_checkmark, 0) + itemChoiceText.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_correct_checkmark, 0) ChoiceColor.CORRECT } false -> { diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index 70bdf3ed27..b3fbe0afc4 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -1,11 +1,28 @@ - \ No newline at end of file + android:orientation="vertical" + android:layout_marginBottom="8dp"> + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 498bce5c13..3daf12f7f8 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -215,4 +215,7 @@ 8dp @dimen/step_content_block_radius + + + 1dp From 4190929b5d84fb2ac892293c67e67cd01a1b59ce Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 19:37:20 +0300 Subject: [PATCH 032/108] use mutate() to provide unique state for each drawable --- .../view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 1cfe90fb89..91303c6a98 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -46,7 +46,7 @@ class ChoicesAdapterDelegate( } private fun bindBackground(data: Choice) { - val shape = itemChoiceText.background as GradientDrawable + val shape = itemChoiceText.background.mutate() as GradientDrawable val choiceColor = inferChoiceColor(data) bindTip(data) setChoiceCorners(shape, data.tip != null) From 14af244045d5dc35b5fcb3e3f2a1e2fd2d28e909 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 26 Jun 2019 21:05:20 +0300 Subject: [PATCH 033/108] hide tip by default --- app/src/main/res/layout/item_choice_quiz.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index b3fbe0afc4..8554b97c23 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -22,6 +22,7 @@ android:paddingBottom="12dp" android:paddingTop="12dp" tools:text="This is a tip" + android:visibility="gone" android:background="@drawable/bg_compound_step_quiz_feedback_tip" android:layout_width="match_parent" android:layout_height="wrap_content" /> From 7bb38a40379ec5cf8220cafe3520704f669652c7 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Fri, 28 Jun 2019 15:11:14 +0300 Subject: [PATCH 034/108] delegate to handle layers in layer drawable --- .../ui/delegate/LayerListDrawableDelegate.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt new file mode 100644 index 0000000000..d9dc06dc3a --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.step_quiz_choice.ui.delegate + +import android.graphics.drawable.LayerDrawable + +class LayerListDrawableDelegate( + private val layerIds: List, + private val layers: LayerDrawable +) { + fun showLayer(visibleLayerId: Int) { + for (layerId in layerIds) { + val layer = layers.findDrawableByLayerId(layerId).mutate() + layer.alpha = if (layerId == visibleLayerId) { + 255 + } else { + 0 + } + layers.setDrawableByLayerId(layerId, layer) + layers.invalidateSelf() + } + } +} \ No newline at end of file From db2cf7d6585ba750e19b44b1a1fd96d80b38d1a1 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Fri, 28 Jun 2019 15:22:51 +0300 Subject: [PATCH 035/108] changed quiz item background --- .../drawable-v21/bg_choice_checked_ripple.xml | 18 +++++++ .../main/res/drawable/bg_choice_quiz_item.xml | 49 +++++++++++++++++++ .../res/drawable/bg_choice_quiz_selected.xml | 13 ----- app/src/main/res/layout/item_choice_quiz.xml | 2 +- 4 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml create mode 100644 app/src/main/res/drawable/bg_choice_quiz_item.xml delete mode 100644 app/src/main/res/drawable/bg_choice_quiz_selected.xml diff --git a/app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml new file mode 100644 index 0000000000..52ecd92e10 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_quiz_item.xml b/app/src/main/res/drawable/bg_choice_quiz_item.xml new file mode 100644 index 0000000000..3d20284124 --- /dev/null +++ b/app/src/main/res/drawable/bg_choice_quiz_item.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_quiz_selected.xml b/app/src/main/res/drawable/bg_choice_quiz_selected.xml deleted file mode 100644 index 7a7a5a488f..0000000000 --- a/app/src/main/res/drawable/bg_choice_quiz_selected.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index 8554b97c23..d002bcb60c 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -9,7 +9,7 @@ android:id="@+id/itemChoiceText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/bg_choice_quiz_selected" + android:background="@drawable/bg_choice_quiz_item" android:padding="16dp" android:textColor="@color/black" tools:text="Hello there bla bla" /> From 2d12094e19aba2bc674733551674ff4ca02e52a9 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Fri, 28 Jun 2019 15:23:30 +0300 Subject: [PATCH 036/108] unchecked ripple for background --- .../bg_choice_not_checked_ripple.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml diff --git a/app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml new file mode 100644 index 0000000000..5ed2c97d43 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file From f0afadcfbfdf013e39b2bcd569c82235a5ca0302 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Fri, 28 Jun 2019 15:44:05 +0300 Subject: [PATCH 037/108] added incorrect choice with tip layer --- app/src/main/res/drawable/bg_choice_quiz_item.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/drawable/bg_choice_quiz_item.xml b/app/src/main/res/drawable/bg_choice_quiz_item.xml index 3d20284124..029d12cf7a 100644 --- a/app/src/main/res/drawable/bg_choice_quiz_item.xml +++ b/app/src/main/res/drawable/bg_choice_quiz_item.xml @@ -46,4 +46,13 @@ + + + + + + + \ No newline at end of file From 12325fbddeda95ecccaf9ae36cd47c53e5b6caad Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Fri, 28 Jun 2019 16:38:33 +0300 Subject: [PATCH 038/108] use layer list delegate in choices adapter delegate --- .../ui/adapter/ChoicesAdapterDelegate.kt | 53 ++++++++----------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 91303c6a98..65d5711379 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -1,16 +1,12 @@ package org.stepik.android.view.step_quiz_choice.ui.adapter -import android.graphics.drawable.GradientDrawable -import android.support.v4.content.ContextCompat +import android.graphics.drawable.LayerDrawable import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.item_choice_quiz.view.* import org.stepic.droid.R -import org.stepic.droid.util.DpPixelsHelper -import org.stepic.droid.util.setRoundedCorners -import org.stepic.droid.util.setTopRoundedCorners import org.stepik.android.presentation.step_quiz_choice.model.Choice -import org.stepik.android.presentation.step_quiz_choice.model.ChoiceColor +import org.stepik.android.view.step_quiz_choice.ui.delegate.LayerListDrawableDelegate import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper @@ -31,9 +27,19 @@ class ChoicesAdapterDelegate( private val itemChoiceText = root.itemChoiceText private val itemChoiceTip = root.itemChoiceTip + private val layerListDrawableDelegate: LayerListDrawableDelegate init { root.setOnClickListener { onClick(itemData as Choice) } + layerListDrawableDelegate = LayerListDrawableDelegate( + listOf( + R.id.not_checked_layer, + R.id.checked_layer, + R.id.correct_layer, + R.id.incorrect_layer, + R.id.incorrect_layer_with_tip + ), + itemChoiceText.background.mutate() as LayerDrawable) } override fun onBind(data: Choice) { @@ -42,27 +48,8 @@ class ChoicesAdapterDelegate( text = data.option setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) } - bindBackground(data) - } - - private fun bindBackground(data: Choice) { - val shape = itemChoiceText.background.mutate() as GradientDrawable - val choiceColor = inferChoiceColor(data) + layerListDrawableDelegate.showLayer(inferChoiceId(data)) bindTip(data) - setChoiceCorners(shape, data.tip != null) - shape.setColor(ContextCompat.getColor(itemView.context, choiceColor.backgroundColor)) - shape.setStroke( - DpPixelsHelper.convertDpToPixel(itemView.context.resources.getDimension(R.dimen.choice_option_stroke_width)).toInt(), - ContextCompat.getColor(itemView.context, choiceColor.strokeColor) - ) - } - - private fun setChoiceCorners(shape: GradientDrawable, hasTip: Boolean) { - if (hasTip) { - shape.setTopRoundedCorners(DpPixelsHelper.convertDpToPixel(8f)) - } else { - shape.setRoundedCorners(DpPixelsHelper.convertDpToPixel(8f)) - } } private fun bindTip(data: Choice) { @@ -77,22 +64,26 @@ class ChoicesAdapterDelegate( } } - private fun inferChoiceColor(data: Choice): ChoiceColor = + private fun inferChoiceId(data: Choice): Int = if (itemView.isSelected) { when (data.correct) { true -> { itemChoiceText.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_correct_checkmark, 0) - ChoiceColor.CORRECT + R.id.correct_layer } false -> { - ChoiceColor.INCORRECT + if (data.tip == null) { + R.id.incorrect_layer + } else { + R.id.incorrect_layer_with_tip + } } else -> { - ChoiceColor.CHECKED + R.id.checked_layer } } } else { - ChoiceColor.NOT_CHECKED + R.id.not_checked_layer } } } \ No newline at end of file From 1e3eaee65df5fba5a3ba02810005ea8b76396722 Mon Sep 17 00:00:00 2001 From: Nikolay Vyahhi Date: Sun, 30 Jun 2019 22:09:35 +0300 Subject: [PATCH 039/108] Update strings.xml #APPS-2019 needs testing --- app/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2602ccbbf1..7a4cef42f3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -540,7 +540,7 @@ ]]> Настройки При необходимости вы можете отключить адаптивные курсы в настройках (Настройки -> Прочее -> Адаптивные курсы) и продолжить обуение в привычном режиме.

+

При необходимости вы можете отключить адаптивные курсы в настройках (Настройки -> Прочее -> Адаптивные курсы) и продолжить обучение в привычном режиме.

]]>
Продолжить @@ -712,4 +712,4 @@ Выберите почтовый клиент - \ No newline at end of file + From db87adc03eb67dadc049587af38c6f6089a8d306 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 10:56:52 +0300 Subject: [PATCH 040/108] form delegates + logics --- .../interactor/StepQuizInteractor.kt | 4 ++ .../step_quiz_text/TextStepQuizPresenter.kt | 41 +++++++++++++++++-- .../step_quiz_text/TextStepQuizView.kt | 9 +++- .../mapper/StepQuizFeedbackMapper.kt | 8 ++-- .../step_quiz/mapper/StepQuizFormMapper.kt | 20 +++++++++ .../step_quiz/ui/delegate/StepQuizDelegate.kt | 41 +++++++++++++++++++ .../ui/delegate/StepQuizFormDelegate.kt | 9 +--- .../ui/delegate/TextStepQuizFormDelegate.kt | 35 ++++++++++++++++ .../ui/fragment/TextStepQuizFragment.kt | 31 ++++++++++++-- 9 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt index b20d007748..7db38183a0 100644 --- a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt @@ -25,6 +25,10 @@ constructor( .filter { it.status == "active" } .switchIfEmpty(attemptRepository.createAttemptForStep(stepId)) + fun createAttempt(stepId: Long): Single = + attemptRepository + .createAttemptForStep(stepId) + fun getSubmission(attemptId: Long): Maybe = submissionRepository .getSubmissionsForAttempt(attemptId) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt index 38312722ab..85d8b05929 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt @@ -5,9 +5,9 @@ import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy import org.stepic.droid.di.qualifiers.BackgroundScheduler import org.stepic.droid.di.qualifiers.MainScheduler -import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepik.android.domain.step_quiz.interactor.StepQuizInteractor import org.stepik.android.model.Reply +import org.stepik.android.model.Submission import org.stepik.android.presentation.base.PresenterBase import javax.inject.Inject @@ -45,8 +45,9 @@ constructor( .flatMap { attempt -> stepQuizInteractor .getSubmission(attempt.id) - .map { TextStepQuizView.State.SubmissionLoaded(attempt, it) as TextStepQuizView.State } - .toSingle(TextStepQuizView.State.AttemptLoaded(attempt)) + .map { TextStepQuizView.SubmissionState.Loaded(it) as TextStepQuizView.SubmissionState } + .toSingle(TextStepQuizView.SubmissionState.Empty) + .map { TextStepQuizView.State.AttemptLoaded(attempt, it) } } .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) @@ -56,7 +57,39 @@ constructor( ) } + fun createAttempt(stepId: Long) { + state = TextStepQuizView.State.Loading + compositeDisposable += stepQuizInteractor + .createAttempt(stepId) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { state = TextStepQuizView.State.AttemptLoaded(it, TextStepQuizView.SubmissionState.Empty) }, + onError = { state = TextStepQuizView.State.NetworkError } + ) + } + fun createSubmission(reply: Reply) { -// val attemptId + val oldState = (state as? TextStepQuizView.State.AttemptLoaded) + ?: return + + if (oldState.submissionState is TextStepQuizView.SubmissionState.Loaded) { + if (oldState.submissionState.submission.status == Submission.Status.WRONG || oldState.submissionState.submission.status == Submission.Status.CORRECT) { + createAttempt(oldState.attempt.step) + return + } + } + + val submission = Submission(attempt = oldState.attempt.id, reply = reply, status = Submission.Status.EVALUATION) + + state = oldState.copy(submissionState = TextStepQuizView.SubmissionState.Loaded(submission)) + compositeDisposable += stepQuizInteractor + .createSubmission(oldState.attempt.id, reply) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { state = oldState.copy(submissionState = TextStepQuizView.SubmissionState.Loaded(it)) }, + onError = { state = oldState } + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt index af1977feab..d99ac05366 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt @@ -7,11 +7,16 @@ interface TextStepQuizView { sealed class State { object Idle : State() object Loading : State() - data class AttemptLoaded(val attempt: Attempt) : State() - data class SubmissionLoaded(val attempt: Attempt, val submission: Submission) : State() + data class AttemptLoaded(val attempt: Attempt, val submissionState: SubmissionState) : State() object NetworkError : State() } + sealed class SubmissionState { + object Empty : SubmissionState() + data class Loaded(val submission: Submission) : SubmissionState() + } + fun setState(state: State) + fun showNetworkError() } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt index 27c8a2ad0b..9f5e67c941 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt @@ -6,13 +6,13 @@ import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizFeedbackMapper { fun mapToStepQuizFeedbackState(state: TextStepQuizView.State): StepQuizFeedbackState = - if (state is TextStepQuizView.State.SubmissionLoaded) { - when (state.submission.status) { + if (state is TextStepQuizView.State.AttemptLoaded && state.submissionState is TextStepQuizView.SubmissionState.Loaded) { + when (state.submissionState.submission.status) { Submission.Status.CORRECT -> - StepQuizFeedbackState.Correct(state.submission.hint) + StepQuizFeedbackState.Correct(state.submissionState.submission.hint?.takeIf(String::isNotEmpty)) Submission.Status.WRONG -> - StepQuizFeedbackState.Wrong(state.submission.hint) + StepQuizFeedbackState.Wrong(state.submissionState.submission.hint?.takeIf(String::isNotEmpty)) Submission.Status.EVALUATION -> StepQuizFeedbackState.Evaluation diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt new file mode 100644 index 0000000000..a7c9e775a5 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt @@ -0,0 +1,20 @@ +package org.stepik.android.view.step_quiz.mapper + +import org.stepik.android.model.Submission +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView + +class StepQuizFormMapper { + fun isQuizEnabled(state: TextStepQuizView.State): Boolean = + state is TextStepQuizView.State.AttemptLoaded && + ( + state.submissionState is TextStepQuizView.SubmissionState.Empty || + state.submissionState is TextStepQuizView.SubmissionState.Loaded && + state.submissionState.submission.status == Submission.Status.LOCAL + ) + + fun isQuizSubmitEnabled(state: TextStepQuizView.State): Boolean = + isQuizEnabled(state) || + state is TextStepQuizView.State.AttemptLoaded && + state.submissionState is TextStepQuizView.SubmissionState.Loaded && + (state.submissionState.submission.status == Submission.Status.CORRECT || state.submissionState.submission.status == Submission.Status.WRONG) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt new file mode 100644 index 0000000000..50929824d5 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -0,0 +1,41 @@ +package org.stepik.android.view.step_quiz.ui.delegate + +import android.view.View +import org.stepik.android.model.Reply +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.view.step_quiz.mapper.StepQuizFeedbackMapper +import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper +import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState +import timber.log.Timber + +class StepQuizDelegate( + private val stepQuizFormDelegate: StepQuizFormDelegate, + private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, + private val submitButton: View, + private val onSubmitReply: (Reply) -> Unit +) { + private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() + private val stepQuizFormMapper = StepQuizFormMapper() + + init { + submitButton.setOnClickListener { trySubmitReply() } + } + + private fun trySubmitReply() { + val validation = stepQuizFormDelegate.validateForm() + if (validation == null) { + onSubmitReply(stepQuizFormDelegate.createReply()) + } else { + stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Validation(validation)) + } + } + + fun setState(state: TextStepQuizView.State) { + stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) + stepQuizFormDelegate.setState(state) + + Timber.d(state.toString()) + + submitButton.isEnabled = stepQuizFormMapper.isQuizSubmitEnabled(state) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt index 224ed3e4df..422f7962cb 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt @@ -3,15 +3,10 @@ package org.stepik.android.view.step_quiz.ui.delegate import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView interface StepQuizFormDelegate { - /** - * If [isEnabled] == false form should block any changes from user - */ - var isEnabled: Boolean - - fun setAttempt(attempt: Attempt) - fun setSubmission(submission: Submission) + fun setState(state: TextStepQuizView.State) /** * Generates reply from current form data diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt new file mode 100644 index 0000000000..c5f654a81f --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -0,0 +1,35 @@ +package org.stepik.android.view.step_quiz_text.ui.delegate + +import android.widget.TextView +import org.stepic.droid.R +import org.stepik.android.model.Reply +import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate + +class TextStepQuizFormDelegate( + private val textField: TextView +) : StepQuizFormDelegate { + private val stepQuizFormMapper = StepQuizFormMapper() + + override fun createReply(): Reply = + Reply(text = textField.text.toString()) + + override fun validateForm(): String? = + if (textField.text.isEmpty()) { + textField.context.getString(R.string.empty_courses_anonymous) // todo add string res + } else { + null + } + + override fun setState(state: TextStepQuizView.State) { + if (state !is TextStepQuizView.State.AttemptLoaded) return + + textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) + textField.text = (state.submissionState as? TextStepQuizView.SubmissionState.Loaded) + ?.submission + ?.reply + ?.text + ?: "" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index abfdbb4eb4..e5d4ea7144 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -3,7 +3,9 @@ package org.stepik.android.view.step_quiz_text.ui.fragment import android.arch.lifecycle.ViewModelProvider import android.arch.lifecycle.ViewModelProviders import android.os.Bundle +import android.support.design.widget.Snackbar import android.support.v4.app.Fragment +import android.support.v4.content.ContextCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -13,11 +15,14 @@ import org.stepic.droid.R import org.stepic.droid.base.App import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument +import org.stepic.droid.util.setTextColor import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.presentation.step_quiz_text.TextStepQuizPresenter import org.stepik.android.presentation.step_quiz_text.TextStepQuizView import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate +import org.stepik.android.view.step_quiz_text.ui.delegate.TextStepQuizFormDelegate import javax.inject.Inject class TextStepQuizFragment : Fragment(), TextStepQuizView { @@ -38,6 +43,8 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { private var stepWrapper: StepPersistentWrapper by argument() private lateinit var stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate + private lateinit var textStepQuizFormDelegate: TextStepQuizFormDelegate + private lateinit var stepQuizDelegate: StepQuizDelegate override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -60,15 +67,31 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - stepQuizSubmit.setOnClickListener { } - stepQuizSubmit.isEnabled = true - stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) + textStepQuizFormDelegate = TextStepQuizFormDelegate(stringStepQuizField) + stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, presenter::createSubmission) + } + + override fun onStart() { + super.onStart() + presenter.attachView(this) + } - stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Wrong(hint = "Lorem ipsum dit aleri poel pelmeni")) + override fun onStop() { + presenter.detachView(this) + super.onStop() } override fun setState(state: TextStepQuizView.State) { + stepQuizDelegate.setState(state) + } + + override fun showNetworkError() { + val view = view ?: return + Snackbar + .make(view, R.string.no_connection, Snackbar.LENGTH_SHORT) + .setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + .show() } } \ No newline at end of file From 8a02ca8f0ee7acc8c62e2c89c6bd397afd67983b Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 11:06:26 +0300 Subject: [PATCH 041/108] add free answer, number & math --- .../ui/factory/StepQuizFragmentFactoryImpl.kt | 5 +++- .../ui/delegate/TextStepQuizFormDelegate.kt | 30 ++++++++++++++++++- .../ui/fragment/TextStepQuizFragment.kt | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index bc04c02520..e6c227a333 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -11,7 +11,10 @@ class StepQuizFragmentFactoryImpl constructor() : StepQuizFragmentFactory { override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment = when (stepPersistentWrapper.step.block?.name) { - AppConstants.TYPE_STRING -> + AppConstants.TYPE_STRING, + AppConstants.TYPE_NUMBER, + AppConstants.TYPE_MATH, + AppConstants.TYPE_FREE_ANSWER -> TextStepQuizFragment.newInstance(stepPersistentWrapper) else -> diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index c5f654a81f..090edb16a2 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -1,17 +1,45 @@ package org.stepik.android.view.step_quiz_text.ui.delegate +import android.text.InputType +import android.view.View import android.widget.TextView +import kotlinx.android.synthetic.main.fragment_step_quiz_text.view.* import org.stepic.droid.R +import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.util.AppConstants import org.stepik.android.model.Reply import org.stepik.android.presentation.step_quiz_text.TextStepQuizView import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate class TextStepQuizFormDelegate( - private val textField: TextView + stepWrapper: StepPersistentWrapper, + containerView: View ) : StepQuizFormDelegate { private val stepQuizFormMapper = StepQuizFormMapper() + private val textField = containerView.stringStepQuizField as TextView + + init { + when (stepWrapper.step.block?.name) { + AppConstants.TYPE_STRING -> { + textField.inputType = InputType.TYPE_CLASS_TEXT + } + + AppConstants.TYPE_NUMBER -> { + textField.inputType = InputType.TYPE_CLASS_NUMBER + } + + AppConstants.TYPE_MATH -> { + textField.inputType = InputType.TYPE_CLASS_TEXT + } + + AppConstants.TYPE_FREE_ANSWER -> { + textField.inputType = InputType.TYPE_CLASS_TEXT + } + } + } + override fun createReply(): Reply = Reply(text = textField.text.toString()) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index e5d4ea7144..55ddf63e32 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -68,7 +68,7 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { super.onViewCreated(view, savedInstanceState) stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) - textStepQuizFormDelegate = TextStepQuizFormDelegate(stringStepQuizField) + textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, presenter::createSubmission) } From 71d299fc350204546bfecf7bd70de1b50963dbc3 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 11:17:02 +0300 Subject: [PATCH 042/108] declare new quizes in step type resolver --- .../org/stepic/droid/util/resolvers/StepTypeResolverImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java index ae1738d17b..c073acaf3f 100644 --- a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java +++ b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java @@ -173,6 +173,9 @@ public boolean isNeedUseOldStepContainer(@NotNull Step step) { case AppConstants.TYPE_TEXT: case AppConstants.TYPE_VIDEO: case AppConstants.TYPE_STRING: + case AppConstants.TYPE_NUMBER: + case AppConstants.TYPE_MATH: + case AppConstants.TYPE_FREE_ANSWER: return false; default: return true; From f90fece192d7ccc994ee4b362511651badd1231c Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 11:32:35 +0300 Subject: [PATCH 043/108] add quiz description --- .../ui/delegate/TextStepQuizFormDelegate.kt | 9 +++++++++ .../res/layout/fragment_step_quiz_text.xml | 19 ++++++++++++++++++- app/src/main/res/values-ru/strings.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 ++++ app/src/main/res/values/styles.xml | 7 +++++++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 090edb16a2..b86c505db6 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -6,6 +6,7 @@ import android.widget.TextView import kotlinx.android.synthetic.main.fragment_step_quiz_text.view.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.ui.util.changeVisibility import org.stepic.droid.util.AppConstants import org.stepik.android.model.Reply import org.stepik.android.presentation.step_quiz_text.TextStepQuizView @@ -19,23 +20,31 @@ class TextStepQuizFormDelegate( private val stepQuizFormMapper = StepQuizFormMapper() private val textField = containerView.stringStepQuizField as TextView + private val quizDescription = containerView.stringStepQuizDescription init { when (stepWrapper.step.block?.name) { AppConstants.TYPE_STRING -> { textField.inputType = InputType.TYPE_CLASS_TEXT + quizDescription.setText(R.string.step_quiz_string_description) + quizDescription.changeVisibility(true) } AppConstants.TYPE_NUMBER -> { textField.inputType = InputType.TYPE_CLASS_NUMBER + quizDescription.setText(R.string.step_quiz_number_description) + quizDescription.changeVisibility(true) } AppConstants.TYPE_MATH -> { textField.inputType = InputType.TYPE_CLASS_TEXT + quizDescription.setText(R.string.step_quiz_math_description) + quizDescription.changeVisibility(true) } AppConstants.TYPE_FREE_ANSWER -> { textField.inputType = InputType.TYPE_CLASS_TEXT + quizDescription.changeVisibility(false) } } } diff --git a/app/src/main/res/layout/fragment_step_quiz_text.xml b/app/src/main/res/layout/fragment_step_quiz_text.xml index 9bca71d3e1..65ab740d34 100644 --- a/app/src/main/res/layout/fragment_step_quiz_text.xml +++ b/app/src/main/res/layout/fragment_step_quiz_text.xml @@ -6,6 +6,23 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f03c1f05ad..e344f0c4e2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -722,6 +722,10 @@ Необходимо заполнить все пробелы перед отправкой + Введите численный ответ + Введите математическую формулу + Напишите текст (строку) + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8b0ad14bc..49cf42b745 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -752,6 +752,10 @@ All blanks should be filled + Enter a number + Enter a math formula + Write an answer in form of a text (string) + Choose email app diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f14b77011f..a44b6f6bcf 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -501,4 +501,11 @@ 16sp 16dp + + From 909c5d150a56fd3ac6aafbe970d412bbb7de0ada Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 11:59:41 +0300 Subject: [PATCH 044/108] TextStepQuizPresenter -> StepQuizPresenter --- .../StepQuizPresenter.kt} | 60 +++++++++++++------ .../StepQuizView.kt} | 4 +- .../view/injection/step/StepComponent.kt | 4 +- .../step_quiz/StepQuizPresentationModule.kt | 19 ++++++ .../step_quiz_text/TextStepQuizModule.kt | 19 ------ .../mapper/StepQuizFeedbackMapper.kt | 6 +- .../step_quiz/mapper/StepQuizFormMapper.kt | 16 ++--- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 4 +- .../ui/delegate/StepQuizFormDelegate.kt | 6 +- .../ui/delegate/TextStepQuizFormDelegate.kt | 8 +-- .../ui/fragment/TextStepQuizFragment.kt | 14 ++--- 11 files changed, 91 insertions(+), 69 deletions(-) rename app/src/main/java/org/stepik/android/presentation/{step_quiz_text/TextStepQuizPresenter.kt => step_quiz/StepQuizPresenter.kt} (59%) rename app/src/main/java/org/stepik/android/presentation/{step_quiz_text/TextStepQuizView.kt => step_quiz/StepQuizView.kt} (86%) create mode 100644 app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt delete mode 100644 app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt similarity index 59% rename from app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt rename to app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index 85d8b05929..c192a9cb6b 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -1,4 +1,4 @@ -package org.stepik.android.presentation.step_quiz_text +package org.stepik.android.presentation.step_quiz import io.reactivex.Scheduler import io.reactivex.rxkotlin.plusAssign @@ -11,7 +11,7 @@ import org.stepik.android.model.Submission import org.stepik.android.presentation.base.PresenterBase import javax.inject.Inject -class TextStepQuizPresenter +class StepQuizPresenter @Inject constructor( private val stepQuizInteractor: StepQuizInteractor, @@ -20,60 +20,78 @@ constructor( private val backgroundScheduler: Scheduler, @MainScheduler private val mainScheduler: Scheduler -) : PresenterBase() { - private var state: TextStepQuizView.State = TextStepQuizView.State.Idle +) : PresenterBase() { + private var state: StepQuizView.State = + StepQuizView.State.Idle set(value) { field = value view?.setState(value) } - override fun attachView(view: TextStepQuizView) { + override fun attachView(view: StepQuizView) { super.attachView(view) view.setState(state) } fun onStepData(stepId: Long) { - if (state == TextStepQuizView.State.Idle) { + if (state == StepQuizView.State.Idle) { fetchAttempt(stepId) } } private fun fetchAttempt(stepId: Long) { - state = TextStepQuizView.State.Loading + state = StepQuizView.State.Loading compositeDisposable += stepQuizInteractor .getAttempt(stepId) .flatMap { attempt -> stepQuizInteractor .getSubmission(attempt.id) - .map { TextStepQuizView.SubmissionState.Loaded(it) as TextStepQuizView.SubmissionState } - .toSingle(TextStepQuizView.SubmissionState.Empty) - .map { TextStepQuizView.State.AttemptLoaded(attempt, it) } + .map { StepQuizView.SubmissionState.Loaded( + it + ) as StepQuizView.SubmissionState + } + .toSingle(StepQuizView.SubmissionState.Empty) + .map { + StepQuizView.State.AttemptLoaded( + attempt, + it + ) + } } .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( onSuccess = { state = it }, - onError = { state = TextStepQuizView.State.NetworkError } + onError = { state = + StepQuizView.State.NetworkError + } ) } fun createAttempt(stepId: Long) { - state = TextStepQuizView.State.Loading + state = StepQuizView.State.Loading compositeDisposable += stepQuizInteractor .createAttempt(stepId) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = TextStepQuizView.State.AttemptLoaded(it, TextStepQuizView.SubmissionState.Empty) }, - onError = { state = TextStepQuizView.State.NetworkError } + onSuccess = { state = + StepQuizView.State.AttemptLoaded( + it, + StepQuizView.SubmissionState.Empty + ) + }, + onError = { state = + StepQuizView.State.NetworkError + } ) } fun createSubmission(reply: Reply) { - val oldState = (state as? TextStepQuizView.State.AttemptLoaded) + val oldState = (state as? StepQuizView.State.AttemptLoaded) ?: return - if (oldState.submissionState is TextStepQuizView.SubmissionState.Loaded) { + if (oldState.submissionState is StepQuizView.SubmissionState.Loaded) { if (oldState.submissionState.submission.status == Submission.Status.WRONG || oldState.submissionState.submission.status == Submission.Status.CORRECT) { createAttempt(oldState.attempt.step) return @@ -82,13 +100,19 @@ constructor( val submission = Submission(attempt = oldState.attempt.id, reply = reply, status = Submission.Status.EVALUATION) - state = oldState.copy(submissionState = TextStepQuizView.SubmissionState.Loaded(submission)) + state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded( + submission + ) + ) compositeDisposable += stepQuizInteractor .createSubmission(oldState.attempt.id, reply) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = oldState.copy(submissionState = TextStepQuizView.SubmissionState.Loaded(it)) }, + onSuccess = { state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded( + it + ) + ) }, onError = { state = oldState } ) } diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt similarity index 86% rename from app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt rename to app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt index d99ac05366..1bb62c6294 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_text/TextStepQuizView.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt @@ -1,9 +1,9 @@ -package org.stepik.android.presentation.step_quiz_text +package org.stepik.android.presentation.step_quiz import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt -interface TextStepQuizView { +interface StepQuizView { sealed class State { object Idle : State() object Loading : State() diff --git a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt index 2f44cff3f9..adcee577f8 100644 --- a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt +++ b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt @@ -4,7 +4,7 @@ import dagger.Subcomponent import org.stepik.android.view.injection.attempt.AttemptDataModule import org.stepik.android.view.injection.step_content.StepContentModule import org.stepik.android.view.injection.step_quiz.StepQuizModule -import org.stepik.android.view.injection.step_quiz_text.TextStepQuizModule +import org.stepik.android.view.injection.step_quiz.StepQuizPresentationModule import org.stepik.android.view.injection.submission.SubmissionDataModule import org.stepik.android.view.step.ui.fragment.StepFragment import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment @@ -14,7 +14,7 @@ import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment StepContentModule::class, StepQuizModule::class, - TextStepQuizModule::class, + StepQuizPresentationModule::class, AttemptDataModule::class, SubmissionDataModule::class ]) diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt new file mode 100644 index 0000000000..e95762f84a --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt @@ -0,0 +1,19 @@ +package org.stepik.android.view.injection.step_quiz + +import android.arch.lifecycle.ViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import org.stepik.android.presentation.base.injection.ViewModelKey +import org.stepik.android.presentation.step_quiz.StepQuizPresenter + +@Module +abstract class StepQuizPresentationModule { + /** + * Presentation + */ + @Binds + @IntoMap + @ViewModelKey(StepQuizPresenter::class) + internal abstract fun bindStepQuizPresenter(textStepQuizPresenter: StepQuizPresenter): ViewModel +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt deleted file mode 100644 index 68bd6dd390..0000000000 --- a/app/src/main/java/org/stepik/android/view/injection/step_quiz_text/TextStepQuizModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.stepik.android.view.injection.step_quiz_text - -import android.arch.lifecycle.ViewModel -import dagger.Binds -import dagger.Module -import dagger.multibindings.IntoMap -import org.stepik.android.presentation.base.injection.ViewModelKey -import org.stepik.android.presentation.step_quiz_text.TextStepQuizPresenter - -@Module -abstract class TextStepQuizModule { - /** - * Presentation - */ - @Binds - @IntoMap - @ViewModelKey(TextStepQuizPresenter::class) - internal abstract fun bindTextStepQuizPresenter(textStepQuizPresenter: TextStepQuizPresenter): ViewModel -} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt index 9f5e67c941..7aab6d867b 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt @@ -1,12 +1,12 @@ package org.stepik.android.view.step_quiz.mapper import org.stepik.android.model.Submission -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizFeedbackMapper { - fun mapToStepQuizFeedbackState(state: TextStepQuizView.State): StepQuizFeedbackState = - if (state is TextStepQuizView.State.AttemptLoaded && state.submissionState is TextStepQuizView.SubmissionState.Loaded) { + fun mapToStepQuizFeedbackState(state: StepQuizView.State): StepQuizFeedbackState = + if (state is StepQuizView.State.AttemptLoaded && state.submissionState is StepQuizView.SubmissionState.Loaded) { when (state.submissionState.submission.status) { Submission.Status.CORRECT -> StepQuizFeedbackState.Correct(state.submissionState.submission.hint?.takeIf(String::isNotEmpty)) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt index a7c9e775a5..741e14fac5 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt @@ -1,20 +1,20 @@ package org.stepik.android.view.step_quiz.mapper import org.stepik.android.model.Submission -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.presentation.step_quiz.StepQuizView class StepQuizFormMapper { - fun isQuizEnabled(state: TextStepQuizView.State): Boolean = - state is TextStepQuizView.State.AttemptLoaded && + fun isQuizEnabled(state: StepQuizView.State): Boolean = + state is StepQuizView.State.AttemptLoaded && ( - state.submissionState is TextStepQuizView.SubmissionState.Empty || - state.submissionState is TextStepQuizView.SubmissionState.Loaded && + state.submissionState is StepQuizView.SubmissionState.Empty || + state.submissionState is StepQuizView.SubmissionState.Loaded && state.submissionState.submission.status == Submission.Status.LOCAL ) - fun isQuizSubmitEnabled(state: TextStepQuizView.State): Boolean = + fun isQuizSubmitEnabled(state: StepQuizView.State): Boolean = isQuizEnabled(state) || - state is TextStepQuizView.State.AttemptLoaded && - state.submissionState is TextStepQuizView.SubmissionState.Loaded && + state is StepQuizView.State.AttemptLoaded && + state.submissionState is StepQuizView.SubmissionState.Loaded && (state.submissionState.submission.status == Submission.Status.CORRECT || state.submissionState.submission.status == Submission.Status.WRONG) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 50929824d5..16afe1cacf 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -2,7 +2,7 @@ package org.stepik.android.view.step_quiz.ui.delegate import android.view.View import org.stepik.android.model.Reply -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.mapper.StepQuizFeedbackMapper import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState @@ -30,7 +30,7 @@ class StepQuizDelegate( } } - fun setState(state: TextStepQuizView.State) { + fun setState(state: StepQuizView.State) { stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) stepQuizFormDelegate.setState(state) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt index 422f7962cb..4fb9ceeb7c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt @@ -1,12 +1,10 @@ package org.stepik.android.view.step_quiz.ui.delegate import org.stepik.android.model.Reply -import org.stepik.android.model.Submission -import org.stepik.android.model.attempts.Attempt -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.presentation.step_quiz.StepQuizView interface StepQuizFormDelegate { - fun setState(state: TextStepQuizView.State) + fun setState(state: StepQuizView.State) /** * Generates reply from current form data diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index b86c505db6..feecaeec71 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -9,7 +9,7 @@ import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.ui.util.changeVisibility import org.stepic.droid.util.AppConstants import org.stepik.android.model.Reply -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView +import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate @@ -59,11 +59,11 @@ class TextStepQuizFormDelegate( null } - override fun setState(state: TextStepQuizView.State) { - if (state !is TextStepQuizView.State.AttemptLoaded) return + override fun setState(state: StepQuizView.State) { + if (state !is StepQuizView.State.AttemptLoaded) return textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) - textField.text = (state.submissionState as? TextStepQuizView.SubmissionState.Loaded) + textField.text = (state.submissionState as? StepQuizView.SubmissionState.Loaded) ?.submission ?.reply ?.text diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 55ddf63e32..faffe8e020 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -17,15 +17,15 @@ import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument import org.stepic.droid.util.setTextColor import org.stepik.android.domain.lesson.model.LessonData -import org.stepik.android.presentation.step_quiz_text.TextStepQuizPresenter -import org.stepik.android.presentation.step_quiz_text.TextStepQuizView -import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState +import org.stepik.android.presentation.step_quiz.StepQuizPresenter +import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate import org.stepik.android.view.step_quiz_text.ui.delegate.TextStepQuizFormDelegate import javax.inject.Inject -class TextStepQuizFragment : Fragment(), TextStepQuizView { +class TextStepQuizFragment : Fragment(), + StepQuizView { companion object { fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = TextStepQuizFragment() @@ -37,7 +37,7 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory - private lateinit var presenter: TextStepQuizPresenter + private lateinit var presenter: StepQuizPresenter private var lessonData: LessonData by argument() private var stepWrapper: StepPersistentWrapper by argument() @@ -50,7 +50,7 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { super.onCreate(savedInstanceState) injectComponent() - presenter = ViewModelProviders.of(this, viewModelFactory).get(TextStepQuizPresenter::class.java) + presenter = ViewModelProviders.of(this, viewModelFactory).get(StepQuizPresenter::class.java) presenter.onStepData(stepWrapper.step.id) } @@ -82,7 +82,7 @@ class TextStepQuizFragment : Fragment(), TextStepQuizView { super.onStop() } - override fun setState(state: TextStepQuizView.State) { + override fun setState(state: StepQuizView.State) { stepQuizDelegate.setState(state) } From fab3d28667be22bb273ce7a430b1add799ee59cd Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 19:51:24 +0300 Subject: [PATCH 045/108] fix free answer description --- .../ui/delegate/TextStepQuizFormDelegate.kt | 36 +++++++++---------- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index feecaeec71..e26228b300 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -23,30 +23,26 @@ class TextStepQuizFormDelegate( private val quizDescription = containerView.stringStepQuizDescription init { - when (stepWrapper.step.block?.name) { - AppConstants.TYPE_STRING -> { - textField.inputType = InputType.TYPE_CLASS_TEXT - quizDescription.setText(R.string.step_quiz_string_description) - quizDescription.changeVisibility(true) - } + val (inputType, textRes) = + when (val blockName = stepWrapper.step.block?.name) { + AppConstants.TYPE_STRING -> + InputType.TYPE_CLASS_TEXT to R.string.step_quiz_string_description - AppConstants.TYPE_NUMBER -> { - textField.inputType = InputType.TYPE_CLASS_NUMBER - quizDescription.setText(R.string.step_quiz_number_description) - quizDescription.changeVisibility(true) - } + AppConstants.TYPE_NUMBER -> + InputType.TYPE_CLASS_NUMBER to R.string.step_quiz_number_description - AppConstants.TYPE_MATH -> { - textField.inputType = InputType.TYPE_CLASS_TEXT - quizDescription.setText(R.string.step_quiz_math_description) - quizDescription.changeVisibility(true) - } + AppConstants.TYPE_MATH -> + InputType.TYPE_CLASS_TEXT to R.string.step_quiz_math_description - AppConstants.TYPE_FREE_ANSWER -> { - textField.inputType = InputType.TYPE_CLASS_TEXT - quizDescription.changeVisibility(false) + AppConstants.TYPE_FREE_ANSWER -> + InputType.TYPE_CLASS_TEXT to R.string.step_quiz_free_answer_description + + else -> + throw IllegalArgumentException("Unsupported block type = $blockName") } - } + + textField.inputType = inputType + quizDescription.setText(textRes) } override fun createReply(): Reply = diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e344f0c4e2..559213b136 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -725,6 +725,7 @@ Введите численный ответ Введите математическую формулу Напишите текст (строку) + Напишите эссе Выберите почтовый клиент diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49cf42b745..cf0d71d735 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -755,6 +755,7 @@ Enter a number Enter a math formula Write an answer in form of a text (string) + Write an essay Choose email app From 81fbf912b639763f5d29b3226571e0a68d4edf35 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 19:51:38 +0300 Subject: [PATCH 046/108] clean up code --- .../step_quiz/StepQuizPresenter.kt | 42 +++++-------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index c192a9cb6b..94125fd69a 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -33,8 +33,9 @@ constructor( view.setState(state) } - fun onStepData(stepId: Long) { - if (state == StepQuizView.State.Idle) { + fun onStepData(stepId: Long, forceUpdate: Boolean = false) { + if (state == StepQuizView.State.Idle || + state == StepQuizView.State.NetworkError && forceUpdate) { fetchAttempt(stepId) } } @@ -46,25 +47,15 @@ constructor( .flatMap { attempt -> stepQuizInteractor .getSubmission(attempt.id) - .map { StepQuizView.SubmissionState.Loaded( - it - ) as StepQuizView.SubmissionState - } + .map { StepQuizView.SubmissionState.Loaded(it) as StepQuizView.SubmissionState } .toSingle(StepQuizView.SubmissionState.Empty) - .map { - StepQuizView.State.AttemptLoaded( - attempt, - it - ) - } + .map { StepQuizView.State.AttemptLoaded(attempt, it) } } .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( onSuccess = { state = it }, - onError = { state = - StepQuizView.State.NetworkError - } + onError = { state = StepQuizView.State.NetworkError } ) } @@ -75,15 +66,8 @@ constructor( .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = - StepQuizView.State.AttemptLoaded( - it, - StepQuizView.SubmissionState.Empty - ) - }, - onError = { state = - StepQuizView.State.NetworkError - } + onSuccess = { state = StepQuizView.State.AttemptLoaded(it, StepQuizView.SubmissionState.Empty) }, + onError = { state = StepQuizView.State.NetworkError } ) } @@ -100,19 +84,13 @@ constructor( val submission = Submission(attempt = oldState.attempt.id, reply = reply, status = Submission.Status.EVALUATION) - state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded( - submission - ) - ) + state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(submission)) compositeDisposable += stepQuizInteractor .createSubmission(oldState.attempt.id, reply) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded( - it - ) - ) }, + onSuccess = { state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(it)) }, onError = { state = oldState } ) } From c7658e9046ebbb5a9ebce3082ebc2b5b5800bc1a Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 1 Jul 2019 19:54:40 +0300 Subject: [PATCH 047/108] add views for network error & loading state --- .../ui/fragment/TextStepQuizFragment.kt | 19 +++++++++++--- .../res/layout/fragment_step_quiz_text.xml | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index faffe8e020..d6107c96a4 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -9,6 +9,7 @@ import android.support.v4.content.ContextCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.error_no_connection_with_button_small.view.* import kotlinx.android.synthetic.main.fragment_step_quiz_text.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R @@ -19,13 +20,14 @@ import org.stepic.droid.util.setTextColor import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate import org.stepik.android.view.step_quiz_text.ui.delegate.TextStepQuizFormDelegate +import org.stepik.android.view.ui.delegate.ViewStateDelegate import javax.inject.Inject -class TextStepQuizFragment : Fragment(), - StepQuizView { +class TextStepQuizFragment : Fragment(), StepQuizView { companion object { fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = TextStepQuizFragment() @@ -46,6 +48,8 @@ class TextStepQuizFragment : Fragment(), private lateinit var textStepQuizFormDelegate: TextStepQuizFormDelegate private lateinit var stepQuizDelegate: StepQuizDelegate + private lateinit var viewStateDelegate: ViewStateDelegate + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) injectComponent() @@ -67,9 +71,17 @@ class TextStepQuizFragment : Fragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewStateDelegate = ViewStateDelegate() + viewStateDelegate.addState() + viewStateDelegate.addState(stepQuizProgress) + viewStateDelegate.addState(stepQuizFeedbackBlocks, stringStepQuizField, stringStepQuizDescription, stepQuizSubmit) + viewStateDelegate.addState(stepQuizNetworkError) + + stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper.step.id, forceUpdate = true) } + stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) - stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, presenter::createSubmission) + stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, presenter) } override fun onStart() { @@ -83,6 +95,7 @@ class TextStepQuizFragment : Fragment(), } override fun setState(state: StepQuizView.State) { + viewStateDelegate.switchState(state) stepQuizDelegate.setState(state) } diff --git a/app/src/main/res/layout/fragment_step_quiz_text.xml b/app/src/main/res/layout/fragment_step_quiz_text.xml index 65ab740d34..f8f362b892 100644 --- a/app/src/main/res/layout/fragment_step_quiz_text.xml +++ b/app/src/main/res/layout/fragment_step_quiz_text.xml @@ -56,6 +56,32 @@ app:layout_constraintEnd_toEndOf="parent" tools:targetApi="lollipop" /> + + + + Date: Tue, 2 Jul 2019 12:34:44 +0300 Subject: [PATCH 048/108] pass presenter to step quiz delegate --- app/src/main/AndroidManifest.xml | 3 +- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1d25448692..4ccd444aeb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -311,7 +311,8 @@ android:windowSoftInputMode="adjustResize" /> + android:name="org.stepik.android.view.lesson.ui.activity.LessonActivity" + android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 16afe1cacf..0799d0ed90 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -1,18 +1,21 @@ package org.stepik.android.view.step_quiz.ui.delegate -import android.view.View +import android.support.annotation.StringRes +import android.widget.TextView +import org.stepic.droid.R import org.stepik.android.model.Reply +import org.stepik.android.model.Submission +import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.mapper.StepQuizFeedbackMapper import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState -import timber.log.Timber class StepQuizDelegate( private val stepQuizFormDelegate: StepQuizFormDelegate, private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, - private val submitButton: View, - private val onSubmitReply: (Reply) -> Unit + private val submitButton: TextView, + private val presenter: StepQuizPresenter ) { private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() private val stepQuizFormMapper = StepQuizFormMapper() @@ -24,7 +27,7 @@ class StepQuizDelegate( private fun trySubmitReply() { val validation = stepQuizFormDelegate.validateForm() if (validation == null) { - onSubmitReply(stepQuizFormDelegate.createReply()) + presenter.createSubmission(stepQuizFormDelegate.createReply()) } else { stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Validation(validation)) } @@ -34,8 +37,20 @@ class StepQuizDelegate( stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) stepQuizFormDelegate.setState(state) - Timber.d(state.toString()) - submitButton.isEnabled = stepQuizFormMapper.isQuizSubmitEnabled(state) + + if (state is StepQuizView.State.AttemptLoaded) { + @StringRes + val submitButtonTextRes = + when ((state.submissionState as? StepQuizView.SubmissionState.Loaded)?.submission?.status) { + Submission.Status.CORRECT, + Submission.Status.WRONG -> + R.string.step_quiz_submit_button_try_again + + else -> + R.string.step_quiz_submit_button_action + } + submitButton.setText(submitButtonTextRes) + } } } \ No newline at end of file From ebe0e3c2f811b4b1043f6ad68ce08a7171606591 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Tue, 2 Jul 2019 15:26:47 +0300 Subject: [PATCH 049/108] add text quiz status compound drawable --- .../view/base/ui/drawable/GravityDrawable.kt | 47 +++++++++++++++++++ .../ui/delegate/TextStepQuizFormDelegate.kt | 37 +++++++++++++-- .../drawable/ic_step_quiz_text_correct.xml | 14 ++++++ .../res/drawable/ic_step_quiz_text_wrong.xml | 18 +++++++ .../res/layout/fragment_step_quiz_text.xml | 2 +- app/src/main/res/values/dimens.xml | 1 + 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt create mode 100644 app/src/main/res/drawable/ic_step_quiz_text_correct.xml create mode 100644 app/src/main/res/drawable/ic_step_quiz_text_wrong.xml diff --git a/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt b/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt new file mode 100644 index 0000000000..4f63c116c7 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt @@ -0,0 +1,47 @@ +package org.stepik.android.view.base.ui.drawable + +import android.annotation.SuppressLint +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.drawable.Drawable +import android.support.annotation.Px +import android.view.Gravity + +class GravityDrawable( + private val drawable: Drawable, + private val gravity: Int, + @Px + private val lineHeight: Int +) : Drawable() { + init { + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + } + + override fun getOpacity(): Int = + drawable.opacity + + override fun setColorFilter(colorFilter: ColorFilter?) { + drawable.colorFilter = colorFilter + } + + override fun setAlpha(alpha: Int) { + drawable.alpha = alpha + } + + override fun getIntrinsicHeight(): Int = + drawable.intrinsicHeight + + override fun getIntrinsicWidth(): Int = + drawable.intrinsicWidth + + @SuppressLint("CanvasSize") + override fun draw(canvas: Canvas) { + canvas.save() + val dy = canvas.height / 2f - lineHeight / 2 + canvas.translate(0f, if (gravity == Gravity.BOTTOM) dy else -dy) + drawable.draw(canvas) + canvas.restore() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index e26228b300..58a7dda423 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -1,15 +1,21 @@ package org.stepik.android.view.step_quiz_text.ui.delegate +import android.support.annotation.DrawableRes +import android.support.v4.view.ViewCompat +import android.support.v4.widget.TextViewCompat +import android.support.v7.content.res.AppCompatResources import android.text.InputType +import android.view.Gravity import android.view.View import android.widget.TextView import kotlinx.android.synthetic.main.fragment_step_quiz_text.view.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper -import org.stepic.droid.ui.util.changeVisibility import org.stepic.droid.util.AppConstants import org.stepik.android.model.Reply +import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.view.base.ui.drawable.GravityDrawable import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate @@ -26,7 +32,7 @@ class TextStepQuizFormDelegate( val (inputType, textRes) = when (val blockName = stepWrapper.step.block?.name) { AppConstants.TYPE_STRING -> - InputType.TYPE_CLASS_TEXT to R.string.step_quiz_string_description + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE to R.string.step_quiz_string_description AppConstants.TYPE_NUMBER -> InputType.TYPE_CLASS_NUMBER to R.string.step_quiz_number_description @@ -35,7 +41,7 @@ class TextStepQuizFormDelegate( InputType.TYPE_CLASS_TEXT to R.string.step_quiz_math_description AppConstants.TYPE_FREE_ANSWER -> - InputType.TYPE_CLASS_TEXT to R.string.step_quiz_free_answer_description + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE to R.string.step_quiz_free_answer_description else -> throw IllegalArgumentException("Unsupported block type = $blockName") @@ -58,11 +64,32 @@ class TextStepQuizFormDelegate( override fun setState(state: StepQuizView.State) { if (state !is StepQuizView.State.AttemptLoaded) return - textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) - textField.text = (state.submissionState as? StepQuizView.SubmissionState.Loaded) + val submission = (state.submissionState as? StepQuizView.SubmissionState.Loaded) ?.submission + + textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) + textField.text = submission ?.reply ?.text ?: "" + + @DrawableRes + val drawableRes = + when (submission?.status) { + Submission.Status.CORRECT -> + R.drawable.ic_step_quiz_text_correct + + Submission.Status.WRONG -> + R.drawable.ic_step_quiz_text_wrong + + else -> + null + } + + val drawable = drawableRes + ?.let { AppCompatResources.getDrawable(textField.context, it) } + ?.let { GravityDrawable(it, Gravity.BOTTOM, textField.resources.getDimensionPixelSize(R.dimen.step_quiz_text_field_min_height)) } + + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textField, null, null, drawable, null) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_step_quiz_text_correct.xml b/app/src/main/res/drawable/ic_step_quiz_text_correct.xml new file mode 100644 index 0000000000..59bb696e59 --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_text_correct.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/ic_step_quiz_text_wrong.xml b/app/src/main/res/drawable/ic_step_quiz_text_wrong.xml new file mode 100644 index 0000000000..b32a703216 --- /dev/null +++ b/app/src/main/res/drawable/ic_step_quiz_text_wrong.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/layout/fragment_step_quiz_text.xml b/app/src/main/res/layout/fragment_step_quiz_text.xml index f8f362b892..aa8a703a66 100644 --- a/app/src/main/res/layout/fragment_step_quiz_text.xml +++ b/app/src/main/res/layout/fragment_step_quiz_text.xml @@ -27,7 +27,7 @@ android:id="@+id/stringStepQuizField" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="48dp" + android:minHeight="@dimen/step_quiz_text_field_min_height" android:background="@drawable/bg_step_quiz_text_field" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ee8f3a4f21..9030858d73 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -221,4 +221,5 @@ 48dp 8dp + 48dp From 7061b85e0e153ea225c18bde750604c260832d34 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 3 Jul 2019 15:53:44 +0300 Subject: [PATCH 050/108] fix stretched progressbar in options --- .../res/layout/progressable_latex_supportable_frame_layout.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/progressable_latex_supportable_frame_layout.xml b/app/src/main/res/layout/progressable_latex_supportable_frame_layout.xml index c16039ae37..77e3330b52 100644 --- a/app/src/main/res/layout/progressable_latex_supportable_frame_layout.xml +++ b/app/src/main/res/layout/progressable_latex_supportable_frame_layout.xml @@ -6,7 +6,7 @@ Date: Wed, 3 Jul 2019 15:54:44 +0300 Subject: [PATCH 051/108] added ProgressLatexView to choice item layout --- .../ui/adapter/ChoicesAdapterDelegate.kt | 20 ++++---- app/src/main/res/layout/item_choice_quiz.xml | 49 +++++++++++++------ 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 65d5711379..56c897f486 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -25,8 +25,10 @@ class ChoicesAdapterDelegate( root: View ) : DelegateViewHolder(root) { - private val itemChoiceText = root.itemChoiceText - private val itemChoiceTip = root.itemChoiceTip + private val itemChoiceContainer = root.itemChoiceContainer + private val itemChoiceCheckmark = root.itemChoiceCheckmark + private val itemChoiceLatex = root.itemChoiceLatex + private val itemChoiceFeedback = root.itemChoiceFeedback private val layerListDrawableDelegate: LayerListDrawableDelegate init { @@ -39,25 +41,23 @@ class ChoicesAdapterDelegate( R.id.incorrect_layer, R.id.incorrect_layer_with_tip ), - itemChoiceText.background.mutate() as LayerDrawable) + itemChoiceContainer.background.mutate() as LayerDrawable) } override fun onBind(data: Choice) { itemView.isSelected = selectionHelper.isSelected(adapterPosition) - itemChoiceText.apply { - text = data.option - setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) - } + itemChoiceCheckmark.visibility = View.INVISIBLE + itemChoiceLatex.setAnyText(data.option) layerListDrawableDelegate.showLayer(inferChoiceId(data)) bindTip(data) } private fun bindTip(data: Choice) { if (data.tip == null) { - itemChoiceTip.visibility = View.GONE + itemChoiceFeedback.visibility = View.GONE } else { - itemChoiceTip.apply { + itemChoiceFeedback.apply { visibility = View.VISIBLE text = data.tip } @@ -68,7 +68,7 @@ class ChoicesAdapterDelegate( if (itemView.isSelected) { when (data.correct) { true -> { - itemChoiceText.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_correct_checkmark, 0) + itemChoiceCheckmark.visibility = View.VISIBLE R.id.correct_layer } false -> { diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index d002bcb60c..ba3b066bfa 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -1,29 +1,50 @@ - + + + android:padding="16dp"> + + + + + + + + + tools:text="This is a tip" /> From cc2c8c54e32f61b846a927673b58ef126131f911 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Wed, 3 Jul 2019 16:38:58 +0300 Subject: [PATCH 052/108] add step quiz restrictions --- .../interactor/StepQuizInteractor.kt | 26 ++++++++++++ .../step_quiz/model/StepQuizRestrictions.kt | 9 +++++ .../step_quiz/StepQuizPresenter.kt | 40 ++++++++++++++----- .../presentation/step_quiz/StepQuizView.kt | 7 +++- .../view/step/ui/fragment/StepFragment.kt | 2 +- .../ui/factory/StepQuizFragmentFactory.kt | 3 +- .../ui/factory/StepQuizFragmentFactoryImpl.kt | 5 ++- .../ui/delegate/TextStepQuizFormDelegate.kt | 31 ++++++++++---- .../ui/fragment/TextStepQuizFragment.kt | 8 ++-- 9 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/domain/step_quiz/model/StepQuizRestrictions.kt diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt index 7db38183a0..e366266cf7 100644 --- a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt @@ -3,9 +3,13 @@ package org.stepik.android.domain.step_quiz.interactor import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single +import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.maybeFirst import org.stepik.android.domain.attempt.repository.AttemptRepository +import org.stepik.android.domain.lesson.model.LessonData +import org.stepik.android.domain.step_quiz.model.StepQuizRestrictions import org.stepik.android.domain.submission.repository.SubmissionRepository +import org.stepik.android.model.DiscountingPolicyType import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt @@ -44,4 +48,26 @@ constructor( .skipWhile { it.status == Submission.Status.EVALUATION } } .firstOrError() + + fun getStepRestrictions(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Single = + getStepSubmissionCount(stepPersistentWrapper.step.id) + .map { submissionCount -> + StepQuizRestrictions( + submissionCount = submissionCount, + maxSubmissionCount = stepPersistentWrapper + .step + .maxSubmissionCount + .takeIf { stepPersistentWrapper.step.hasSubmissionRestriction } + ?: -1, + discountingPolicyType = lessonData + .section + ?.discountingPolicy + ?: DiscountingPolicyType.NoDiscount + ) + } + + private fun getStepSubmissionCount(stepId: Long): Single = + submissionRepository + .getSubmissionsForStep(stepId) + .map { it.size } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/model/StepQuizRestrictions.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/model/StepQuizRestrictions.kt new file mode 100644 index 0000000000..7ab03d698a --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/model/StepQuizRestrictions.kt @@ -0,0 +1,9 @@ +package org.stepik.android.domain.step_quiz.model + +import org.stepik.android.model.DiscountingPolicyType + +data class StepQuizRestrictions( + val submissionCount: Int, + val maxSubmissionCount: Int, + val discountingPolicyType: DiscountingPolicyType +) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index 94125fd69a..d1a854f4e3 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -1,10 +1,14 @@ package org.stepik.android.presentation.step_quiz import io.reactivex.Scheduler +import io.reactivex.Single +import io.reactivex.rxkotlin.Singles.zip import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.subscribeBy import org.stepic.droid.di.qualifiers.BackgroundScheduler import org.stepic.droid.di.qualifiers.MainScheduler +import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.domain.step_quiz.interactor.StepQuizInteractor import org.stepik.android.model.Reply import org.stepik.android.model.Submission @@ -33,23 +37,22 @@ constructor( view.setState(state) } - fun onStepData(stepId: Long, forceUpdate: Boolean = false) { + fun onStepData(stepWrapper: StepPersistentWrapper, lessonData: LessonData, forceUpdate: Boolean = false) { if (state == StepQuizView.State.Idle || state == StepQuizView.State.NetworkError && forceUpdate) { - fetchAttempt(stepId) + fetchAttempt(stepWrapper, lessonData) } } - private fun fetchAttempt(stepId: Long) { + private fun fetchAttempt(stepWrapper: StepPersistentWrapper, lessonData: LessonData) { state = StepQuizView.State.Loading compositeDisposable += stepQuizInteractor - .getAttempt(stepId) + .getAttempt(stepWrapper.step.id) .flatMap { attempt -> - stepQuizInteractor - .getSubmission(attempt.id) - .map { StepQuizView.SubmissionState.Loaded(it) as StepQuizView.SubmissionState } - .toSingle(StepQuizView.SubmissionState.Empty) - .map { StepQuizView.State.AttemptLoaded(attempt, it) } + zip(getSubmissionState(attempt.id), stepQuizInteractor.getStepRestrictions(stepWrapper, lessonData)) + .map { (submissionState, stepRestrictions) -> + StepQuizView.State.AttemptLoaded(attempt, submissionState, stepRestrictions) + } } .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) @@ -59,14 +62,23 @@ constructor( ) } + private fun getSubmissionState(attemptId: Long): Single = + stepQuizInteractor + .getSubmission(attemptId) + .map { StepQuizView.SubmissionState.Loaded(it) as StepQuizView.SubmissionState } + .toSingle(StepQuizView.SubmissionState.Empty) + fun createAttempt(stepId: Long) { + val oldState = (state as? StepQuizView.State.AttemptLoaded) + ?: return + state = StepQuizView.State.Loading compositeDisposable += stepQuizInteractor .createAttempt(stepId) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = StepQuizView.State.AttemptLoaded(it, StepQuizView.SubmissionState.Empty) }, + onSuccess = { state = StepQuizView.State.AttemptLoaded(it, StepQuizView.SubmissionState.Empty, oldState.restrictions) }, onError = { state = StepQuizView.State.NetworkError } ) } @@ -90,7 +102,13 @@ constructor( .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( - onSuccess = { state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(it)) }, + onSuccess = { newSubmission -> + state = oldState + .copy( + submissionState = StepQuizView.SubmissionState.Loaded(newSubmission), + restrictions = oldState.restrictions.copy(submissionCount = oldState.restrictions.submissionCount + 1) + ) + }, onError = { state = oldState } ) } diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt index 1bb62c6294..6202343dcb 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizView.kt @@ -2,12 +2,17 @@ package org.stepik.android.presentation.step_quiz import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt +import org.stepik.android.domain.step_quiz.model.StepQuizRestrictions interface StepQuizView { sealed class State { object Idle : State() object Loading : State() - data class AttemptLoaded(val attempt: Attempt, val submissionState: SubmissionState) : State() + data class AttemptLoaded( + val attempt: Attempt, + val submissionState: SubmissionState, + val restrictions: StepQuizRestrictions + ) : State() object NetworkError : State() } diff --git a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt index dc8b1e03a3..775aa710ab 100644 --- a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt @@ -132,7 +132,7 @@ class StepFragment : Fragment(), StepView { if (isStepHasQuiz && childFragmentManager.findFragmentByTag(STEP_QUIZ_FRAGMENT_TAG) == null) { childFragmentManager .beginTransaction() - .add(R.id.stepQuizContainer, stepQuizFragmentFactory.createStepQuizFragment(stepWrapper), STEP_QUIZ_FRAGMENT_TAG) + .add(R.id.stepQuizContainer, stepQuizFragmentFactory.createStepQuizFragment(stepWrapper, lessonData), STEP_QUIZ_FRAGMENT_TAG) .commitNow() } } diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactory.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactory.kt index e7bc35967b..c6e21a7e0c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactory.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactory.kt @@ -2,8 +2,9 @@ package org.stepik.android.view.step_quiz.ui.factory import android.support.v4.app.Fragment import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepik.android.domain.lesson.model.LessonData interface StepQuizFragmentFactory { - fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment + fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Fragment fun isStepCanHaveQuiz(stepPersistentWrapper: StepPersistentWrapper): Boolean } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index e6c227a333..2bffdae7d4 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -3,19 +3,20 @@ package org.stepik.android.view.step_quiz.ui.factory import android.support.v4.app.Fragment import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants +import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment import javax.inject.Inject class StepQuizFragmentFactoryImpl @Inject constructor() : StepQuizFragmentFactory { - override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper): Fragment = + override fun createStepQuizFragment(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Fragment = when (stepPersistentWrapper.step.block?.name) { AppConstants.TYPE_STRING, AppConstants.TYPE_NUMBER, AppConstants.TYPE_MATH, AppConstants.TYPE_FREE_ANSWER -> - TextStepQuizFragment.newInstance(stepPersistentWrapper) + TextStepQuizFragment.newInstance(stepPersistentWrapper, lessonData) else -> Fragment() diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 58a7dda423..6505725462 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -1,7 +1,6 @@ package org.stepik.android.view.step_quiz_text.ui.delegate import android.support.annotation.DrawableRes -import android.support.v4.view.ViewCompat import android.support.v4.widget.TextViewCompat import android.support.v7.content.res.AppCompatResources import android.text.InputType @@ -20,7 +19,7 @@ import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate class TextStepQuizFormDelegate( - stepWrapper: StepPersistentWrapper, + private val stepWrapper: StepPersistentWrapper, containerView: View ) : StepQuizFormDelegate { private val stepQuizFormMapper = StepQuizFormMapper() @@ -52,7 +51,16 @@ class TextStepQuizFormDelegate( } override fun createReply(): Reply = - Reply(text = textField.text.toString()) + when (stepWrapper.step.block?.name) { + AppConstants.TYPE_NUMBER -> + Reply(number = textField.text.toString()) + + AppConstants.TYPE_MATH -> + Reply(formula = textField.text.toString()) + + else -> + Reply(text = textField.text.toString()) + } override fun validateForm(): String? = if (textField.text.isEmpty()) { @@ -67,11 +75,20 @@ class TextStepQuizFormDelegate( val submission = (state.submissionState as? StepQuizView.SubmissionState.Loaded) ?.submission + val reply = submission?.reply + textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) - textField.text = submission - ?.reply - ?.text - ?: "" + textField.text = + when(stepWrapper.step.block?.name) { + AppConstants.TYPE_NUMBER -> + reply?.number + + AppConstants.TYPE_MATH -> + reply?.formula + + else -> + reply?.text ?: "" + } @DrawableRes val drawableRes = diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index d6107c96a4..7600d1be3d 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -20,7 +20,6 @@ import org.stepic.droid.util.setTextColor import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView -import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate import org.stepik.android.view.step_quiz_text.ui.delegate.TextStepQuizFormDelegate @@ -29,10 +28,11 @@ import javax.inject.Inject class TextStepQuizFragment : Fragment(), StepQuizView { companion object { - fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = + fun newInstance(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Fragment = TextStepQuizFragment() .apply { this.stepWrapper = stepPersistentWrapper + this.lessonData = lessonData } } @@ -55,7 +55,7 @@ class TextStepQuizFragment : Fragment(), StepQuizView { injectComponent() presenter = ViewModelProviders.of(this, viewModelFactory).get(StepQuizPresenter::class.java) - presenter.onStepData(stepWrapper.step.id) + presenter.onStepData(stepWrapper, lessonData) } private fun injectComponent() { @@ -77,7 +77,7 @@ class TextStepQuizFragment : Fragment(), StepQuizView { viewStateDelegate.addState(stepQuizFeedbackBlocks, stringStepQuizField, stringStepQuizDescription, stepQuizSubmit) viewStateDelegate.addState(stepQuizNetworkError) - stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper.step.id, forceUpdate = true) } + stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) From 454a91baf2eac371e4f25261a55f02e5c32f2f9d Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Wed, 3 Jul 2019 18:34:24 +0300 Subject: [PATCH 053/108] resolve quiz action button text & state --- .../step_quiz/mapper/StepQuizFormMapper.kt | 3 +- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 43 +++++++++++++------ .../layout/view_step_quiz_submit_button.xml | 2 +- app/src/main/res/values-ru/plurals.xml | 6 +++ app/src/main/res/values-ru/strings.xml | 6 ++- app/src/main/res/values/plurals.xml | 4 ++ app/src/main/res/values/strings.xml | 6 ++- 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt index 741e14fac5..d8d6b63fed 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt @@ -16,5 +16,6 @@ class StepQuizFormMapper { isQuizEnabled(state) || state is StepQuizView.State.AttemptLoaded && state.submissionState is StepQuizView.SubmissionState.Loaded && - (state.submissionState.submission.status == Submission.Status.CORRECT || state.submissionState.submission.status == Submission.Status.WRONG) + state.submissionState.submission.status.let { it == Submission.Status.CORRECT || it == Submission.Status.WRONG } && + with(state.restrictions) { maxSubmissionCount < 0 || maxSubmissionCount > submissionCount } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 0799d0ed90..ea65b3de40 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -1,9 +1,7 @@ package org.stepik.android.view.step_quiz.ui.delegate -import android.support.annotation.StringRes import android.widget.TextView import org.stepic.droid.R -import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView @@ -17,6 +15,8 @@ class StepQuizDelegate( private val submitButton: TextView, private val presenter: StepQuizPresenter ) { + private val context = submitButton.context + private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() private val stepQuizFormMapper = StepQuizFormMapper() @@ -40,17 +40,34 @@ class StepQuizDelegate( submitButton.isEnabled = stepQuizFormMapper.isQuizSubmitEnabled(state) if (state is StepQuizView.State.AttemptLoaded) { - @StringRes - val submitButtonTextRes = - when ((state.submissionState as? StepQuizView.SubmissionState.Loaded)?.submission?.status) { - Submission.Status.CORRECT, - Submission.Status.WRONG -> - R.string.step_quiz_submit_button_try_again - - else -> - R.string.step_quiz_submit_button_action - } - submitButton.setText(submitButtonTextRes) + submitButton.text = resolveQuizActionButtonText(state) } } + + private fun resolveQuizActionButtonText(state: StepQuizView.State.AttemptLoaded): String = + with(state.restrictions) { + val isSubmissionInTerminalState = + (state.submissionState as? StepQuizView.SubmissionState.Loaded) + ?.submission + ?.status + .let { it == Submission.Status.CORRECT || it == Submission.Status.WRONG } + + if (isSubmissionInTerminalState) { + if (maxSubmissionCount in 0 until submissionCount) { + context.getString(R.string.step_quiz_action_button_no_submissions) + } else { + context.getString(R.string.step_quiz_action_button_try_again) + } + } else { + if (maxSubmissionCount > submissionCount) { + val submissionsLeft = maxSubmissionCount - submissionCount + context.getString( + R.string.step_quiz_action_button_submit_with_counter, + context.resources.getQuantityString(R.plurals.submissions, submissionsLeft, submissionsLeft) + ) + } else { + context.getString(R.string.step_quiz_action_button_submit) + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/view_step_quiz_submit_button.xml b/app/src/main/res/layout/view_step_quiz_submit_button.xml index e902abbe9e..cca246d6d7 100644 --- a/app/src/main/res/layout/view_step_quiz_submit_button.xml +++ b/app/src/main/res/layout/view_step_quiz_submit_button.xml @@ -6,7 +6,7 @@ android:layout_height="@dimen/step_submit_button_height" android:background="@drawable/bg_step_submit_button" - android:text="@string/step_quiz_submit_button_action" + android:text="@string/step_quiz_action_button_submit" android:textSize="16sp" android:textColor="@color/color_step_submit_button_text" android:gravity="center" /> \ No newline at end of file diff --git a/app/src/main/res/values-ru/plurals.xml b/app/src/main/res/values-ru/plurals.xml index ceb64b4ff5..d98f352a10 100644 --- a/app/src/main/res/values-ru/plurals.xml +++ b/app/src/main/res/values-ru/plurals.xml @@ -54,4 +54,10 @@ %d баллов %d баллов + + %d попытка + %d попытки + %d попыток + %d попыток + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 559213b136..fe38c3bcb1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -710,8 +710,10 @@ Видео не было добавлено или еще не обработано - Отправить - Попробовать снова + Отправить + Отправить (осталось %s) + Попробовать снова + Попытки закончились Введите ответ… diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 1045f0c900..8cb4454e42 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -35,4 +35,8 @@ %d point %d points + + %d submission + %d submissions + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf0d71d735..2e28d48223 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -740,8 +740,10 @@ Video has not been uploaded or processed yet - Submit - Try again + Submit + Submit (%s left) + Try again + No submissions left Type your answer… From 0cc330cb9e945735225110d6bbfb7554de736bff Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 11:32:34 +0300 Subject: [PATCH 054/108] refactor quiz state flow --- .../step_quiz/StepQuizPresenter.kt | 7 --- .../step_quiz/model/ReplyResult.kt | 8 ++++ .../step_quiz/mapper/StepQuizFormMapper.kt | 21 ++++----- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 46 ++++++++++--------- .../ui/delegate/StepQuizFormDelegate.kt | 12 ++--- .../ui/delegate/TextStepQuizFormDelegate.kt | 46 ++++++++++--------- .../ui/fragment/TextStepQuizFragment.kt | 4 +- 7 files changed, 72 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index d1a854f4e3..df41c84c35 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -87,13 +87,6 @@ constructor( val oldState = (state as? StepQuizView.State.AttemptLoaded) ?: return - if (oldState.submissionState is StepQuizView.SubmissionState.Loaded) { - if (oldState.submissionState.submission.status == Submission.Status.WRONG || oldState.submissionState.submission.status == Submission.Status.CORRECT) { - createAttempt(oldState.attempt.step) - return - } - } - val submission = Submission(attempt = oldState.attempt.id, reply = reply, status = Submission.Status.EVALUATION) state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(submission)) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt new file mode 100644 index 0000000000..e8ec3d2048 --- /dev/null +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt @@ -0,0 +1,8 @@ +package org.stepik.android.presentation.step_quiz.model + +import org.stepik.android.model.Reply + +sealed class ReplyResult { + data class Success(val reply: Reply): ReplyResult() + data class Error(val message: String): ReplyResult() +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt index d8d6b63fed..3677bb13c7 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt @@ -4,18 +4,17 @@ import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView class StepQuizFormMapper { - fun isQuizEnabled(state: StepQuizView.State): Boolean = - state is StepQuizView.State.AttemptLoaded && - ( - state.submissionState is StepQuizView.SubmissionState.Empty || - state.submissionState is StepQuizView.SubmissionState.Loaded && - state.submissionState.submission.status == Submission.Status.LOCAL - ) + fun isQuizEnabled(state: StepQuizView.State.AttemptLoaded): Boolean = + state.submissionState is StepQuizView.SubmissionState.Empty || + state.submissionState is StepQuizView.SubmissionState.Loaded && + state.submissionState.submission.status == Submission.Status.LOCAL - fun isQuizSubmitEnabled(state: StepQuizView.State): Boolean = + fun isQuizActionEnabled(state: StepQuizView.State.AttemptLoaded): Boolean = isQuizEnabled(state) || - state is StepQuizView.State.AttemptLoaded && - state.submissionState is StepQuizView.SubmissionState.Loaded && - state.submissionState.submission.status.let { it == Submission.Status.CORRECT || it == Submission.Status.WRONG } && + isSubmissionInTerminalState(state) && with(state.restrictions) { maxSubmissionCount < 0 || maxSubmissionCount > submissionCount } + + fun isSubmissionInTerminalState(state: StepQuizView.State.AttemptLoaded): Boolean = + state.submissionState is StepQuizView.SubmissionState.Loaded && + state.submissionState.submission.status.let { it == Submission.Status.CORRECT || it == Submission.Status.WRONG } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index ea65b3de40..3dd69562be 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -2,9 +2,9 @@ package org.stepik.android.view.step_quiz.ui.delegate import android.widget.TextView import org.stepic.droid.R -import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.view.step_quiz.mapper.StepQuizFeedbackMapper import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState @@ -12,47 +12,49 @@ import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizDelegate( private val stepQuizFormDelegate: StepQuizFormDelegate, private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, - private val submitButton: TextView, + private val actionButton: TextView, private val presenter: StepQuizPresenter ) { - private val context = submitButton.context + private val context = actionButton.context private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() private val stepQuizFormMapper = StepQuizFormMapper() + private var currentState: StepQuizView.State.AttemptLoaded? = null + init { - submitButton.setOnClickListener { trySubmitReply() } + actionButton.setOnClickListener { onActionButtonClicked() } } - private fun trySubmitReply() { - val validation = stepQuizFormDelegate.validateForm() - if (validation == null) { - presenter.createSubmission(stepQuizFormDelegate.createReply()) + private fun onActionButtonClicked() { + val state = currentState ?: return + + if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { + presenter.createAttempt(state.attempt.step) } else { - stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Validation(validation)) + when (val replyResult = stepQuizFormDelegate.createReply()) { + is ReplyResult.Success -> + presenter.createSubmission(replyResult.reply) + + is ReplyResult.Error -> + stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Validation(replyResult.message)) + } } } - fun setState(state: StepQuizView.State) { + fun setState(state: StepQuizView.State.AttemptLoaded) { + currentState = state + stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) stepQuizFormDelegate.setState(state) - submitButton.isEnabled = stepQuizFormMapper.isQuizSubmitEnabled(state) - - if (state is StepQuizView.State.AttemptLoaded) { - submitButton.text = resolveQuizActionButtonText(state) - } + actionButton.isEnabled = stepQuizFormMapper.isQuizActionEnabled(state) + actionButton.text = resolveQuizActionButtonText(state) } private fun resolveQuizActionButtonText(state: StepQuizView.State.AttemptLoaded): String = with(state.restrictions) { - val isSubmissionInTerminalState = - (state.submissionState as? StepQuizView.SubmissionState.Loaded) - ?.submission - ?.status - .let { it == Submission.Status.CORRECT || it == Submission.Status.WRONG } - - if (isSubmissionInTerminalState) { + if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { if (maxSubmissionCount in 0 until submissionCount) { context.getString(R.string.step_quiz_action_button_no_submissions) } else { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt index 4fb9ceeb7c..2b2dc53afd 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFormDelegate.kt @@ -1,19 +1,13 @@ package org.stepik.android.view.step_quiz.ui.delegate -import org.stepik.android.model.Reply import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.presentation.step_quiz.model.ReplyResult interface StepQuizFormDelegate { - fun setState(state: StepQuizView.State) + fun setState(state: StepQuizView.State.AttemptLoaded) /** * Generates reply from current form data */ - fun createReply(): Reply - - /** - * Validates form for ability to create a reply - * @returns null if validation successful or message string otherwise - */ - fun validateForm(): String? + fun createReply(): ReplyResult } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 6505725462..6a67d8a043 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -14,6 +14,7 @@ import org.stepic.droid.util.AppConstants import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.view.base.ui.drawable.GravityDrawable import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate @@ -22,6 +23,8 @@ class TextStepQuizFormDelegate( private val stepWrapper: StepPersistentWrapper, containerView: View ) : StepQuizFormDelegate { + private val context = containerView.context + private val stepQuizFormMapper = StepQuizFormMapper() private val textField = containerView.stringStepQuizField as TextView @@ -50,28 +53,27 @@ class TextStepQuizFormDelegate( quizDescription.setText(textRes) } - override fun createReply(): Reply = - when (stepWrapper.step.block?.name) { - AppConstants.TYPE_NUMBER -> - Reply(number = textField.text.toString()) - - AppConstants.TYPE_MATH -> - Reply(formula = textField.text.toString()) - - else -> - Reply(text = textField.text.toString()) - } - - override fun validateForm(): String? = - if (textField.text.isEmpty()) { - textField.context.getString(R.string.empty_courses_anonymous) // todo add string res - } else { - null + override fun createReply(): ReplyResult = + textField.text.toString().let { value -> + if (value.isNotEmpty()) { + val reply = + when (stepWrapper.step.block?.name) { + AppConstants.TYPE_NUMBER -> + Reply(number = value) + + AppConstants.TYPE_MATH -> + Reply(formula = value) + + else -> + Reply(text = value) + } + ReplyResult.Success(reply) + } else { + ReplyResult.Error(context.getString(R.string.empty_courses_anonymous)) + } } - override fun setState(state: StepQuizView.State) { - if (state !is StepQuizView.State.AttemptLoaded) return - + override fun setState(state: StepQuizView.State.AttemptLoaded) { val submission = (state.submissionState as? StepQuizView.SubmissionState.Loaded) ?.submission @@ -87,8 +89,8 @@ class TextStepQuizFormDelegate( reply?.formula else -> - reply?.text ?: "" - } + reply?.text + } ?: "" @DrawableRes val drawableRes = diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 7600d1be3d..904e01a4f5 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -96,7 +96,9 @@ class TextStepQuizFragment : Fragment(), StepQuizView { override fun setState(state: StepQuizView.State) { viewStateDelegate.switchState(state) - stepQuizDelegate.setState(state) + if (state is StepQuizView.State.AttemptLoaded) { + stepQuizDelegate.setState(state) + } } override fun showNetworkError() { From 1f9223306e5c99fc8507f00323597703967bc213 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 14:11:20 +0300 Subject: [PATCH 055/108] sync reply state --- .../step_quiz/StepQuizPresenter.kt | 8 +++ .../step_quiz/ui/delegate/StepQuizDelegate.kt | 11 ++++ .../ui/fragment/TextStepQuizFragment.kt | 5 +- .../res/layout/fragment_step_quiz_text.xml | 19 ++++++- .../java/org/stepik/android/model/Reply.kt | 52 +++++++++++-------- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index df41c84c35..131045267b 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -105,4 +105,12 @@ constructor( onError = { state = oldState } ) } + + fun syncReplyState(reply: Reply) { + val oldState = (state as? StepQuizView.State.AttemptLoaded) + ?: return + + val submission = Submission(attempt = oldState.attempt.id, reply = reply, status = Submission.Status.LOCAL) + state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(submission)) + } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 3dd69562be..5bcae95127 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -13,6 +13,7 @@ class StepQuizDelegate( private val stepQuizFormDelegate: StepQuizFormDelegate, private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, private val actionButton: TextView, + private val stepQuizDiscountingPolicy: TextView, private val presenter: StepQuizPresenter ) { private val context = actionButton.context @@ -72,4 +73,14 @@ class StepQuizDelegate( } } } + + fun syncReplyState() { + if (stepQuizFormMapper.isSubmissionInTerminalState(currentState ?: return)) return + + val reply = (stepQuizFormDelegate.createReply() as? ReplyResult.Success) + ?.reply + ?: return + + presenter.syncReplyState(reply) + } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 904e01a4f5..185f14eeba 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -74,14 +74,14 @@ class TextStepQuizFragment : Fragment(), StepQuizView { viewStateDelegate = ViewStateDelegate() viewStateDelegate.addState() viewStateDelegate.addState(stepQuizProgress) - viewStateDelegate.addState(stepQuizFeedbackBlocks, stringStepQuizField, stringStepQuizDescription, stepQuizSubmit) + viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, stringStepQuizField, stringStepQuizDescription, stepQuizSubmit) viewStateDelegate.addState(stepQuizNetworkError) stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) - stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, presenter) + stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, stepQuizDiscountingPolicy, presenter) } override fun onStart() { @@ -91,6 +91,7 @@ class TextStepQuizFragment : Fragment(), StepQuizView { override fun onStop() { presenter.detachView(this) + stepQuizDelegate.syncReplyState() super.onStop() } diff --git a/app/src/main/res/layout/fragment_step_quiz_text.xml b/app/src/main/res/layout/fragment_step_quiz_text.xml index aa8a703a66..72da521b5c 100644 --- a/app/src/main/res/layout/fragment_step_quiz_text.xml +++ b/app/src/main/res/layout/fragment_step_quiz_text.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent"> + + ? = null, - val text: String? = null, - val attachments: List? = null, - val formula: String? = null, - val number: String? = null, - val ordering: List? = null, - val language: String? = null, - val code: String? = null, - - @SerializedName("solve_sql") - val solveSql: String? = null, - - val blanks: List? = null, - var tableChoices: List? = null //this is not serialize by default, because field 'choices' is already created by different type + @SerializedName("choices") + val choices: List? = null, + @SerializedName("text") + val text: String? = null, + @SerializedName("attachments") + val attachments: List? = null, + @SerializedName("formula") + val formula: String? = null, + @SerializedName("number") + val number: String? = null, + @SerializedName("ordering") + val ordering: List? = null, + @SerializedName("language") + val language: String? = null, + @SerializedName("code") + val code: String? = null, + + @SerializedName("solve_sql") + val solveSql: String? = null, + + @SerializedName("blanks") + val blanks: List? = null, + var tableChoices: List? = null //this is not serialize by default, because field 'choices' is already created by different type ) - class ReplyWrapper(val reply: Reply?) - data class TableChoiceAnswer( - @SerializedName("name_row") - val nameRow: String, - val columns: List + @SerializedName("name_row") + val nameRow: String, + @SerializedName("columns") + val columns: List ) { data class Cell( - val name: String, - var answer: Boolean + @SerializedName("name") + val name: String, + @SerializedName("answer") + var answer: Boolean ) } From 12fab2ec6282ee26402ff7d2be5e01c1f8d963dd Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 4 Jul 2019 14:33:55 +0300 Subject: [PATCH 056/108] choice quiz form and fragment draft --- .../ui/delegate/ChoiceQuizFormDelegate.kt | 43 +++++++++++-------- .../ui/fragment/ChoiceStepQuizFragment.kt | 11 ++++- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 93534b4766..1a235edbdc 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -6,6 +6,8 @@ import kotlinx.android.synthetic.main.view_choice_quiz_attempt.view.* import org.stepik.android.model.Reply import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt +import org.stepik.android.presentation.step_quiz_choice.model.Choice +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate import ru.nobird.android.ui.adapterssupport.DefaultDelegateAdapter import ru.nobird.android.ui.adapterssupport.selection.MultipleChoiceSelectionHelper @@ -14,8 +16,8 @@ import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelpe class ChoiceQuizFormDelegate( private val choiceAttemptView: View -) : StepQuizFormDelegate() { - private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() +) : StepQuizFormDelegate { + private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() private lateinit var selectionHelper: SelectionHelper init { @@ -27,10 +29,10 @@ class ChoiceQuizFormDelegate( override var isEnabled: Boolean = true - override fun setAttempt(attempt: Attempt?) { - val dataSet = attempt?.dataset + override fun setAttempt(attempt: Attempt) { + val dataSet = attempt.dataset dataSet?.options?.let { options -> - choicesAdapter.items = options + choicesAdapter.items = options.map { Choice(it) } selectionHelper = if (dataSet.isMultipleChoice) { MultipleChoiceSelectionHelper(choicesAdapter) } else { @@ -40,11 +42,23 @@ class ChoiceQuizFormDelegate( } } - override fun setSubmission(submission: Submission?) { - submission?.reply?.choices?.let { setChoices(it)} + override fun setSubmission(submission: Submission) { + submission.reply?.choices?.let { setChoices(it)} } - private fun handleChoiceClick(choice: String) { + override fun createReply(): Reply { + val selection = (0 until choicesAdapter.itemCount) + .map { + selectionHelper.isSelected(it) + } + return Reply(choices = selection) + } + + override fun validateForm(): String? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + private fun handleChoiceClick(choice: Choice) { if (!isEnabled) return when (selectionHelper) { is SingleChoiceSelectionHelper -> { @@ -62,16 +76,11 @@ class ChoiceQuizFormDelegate( (0 until choices.size).forEach {pos -> if (choices[pos]) { selectionHelper.select(pos) + choicesAdapter.items[pos].apply { + correct = true + // tip = "This is a tip\n new line" + } } } } - - val reply: Reply - get() { - val selection = (0 until choicesAdapter.itemCount) - .map { - selectionHelper.isSelected(it) - } - return Reply(choices = selection) - } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index 5130430f8d..d39e9a504a 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -9,6 +9,8 @@ import kotlinx.android.synthetic.main.fragment_step_quiz_choice.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument +import org.stepik.android.model.Reply +import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt import org.stepik.android.model.attempts.Dataset import org.stepik.android.model.attempts.DatasetWrapper @@ -35,10 +37,15 @@ class ChoiceStepQuizFragment: Fragment() { choiceQuizFormDelegate.setAttempt(Attempt( _dataset = DatasetWrapper( Dataset( - options = listOf("Variant 1", "Variant 2", "Variant 3\nExtra line", "Variant 4"), - isMultipleChoice = false + options = listOf("Variant 1", "\$P_{1000, 0.002}(7) \\approx P_{\\lambda}(7) = \\frac{\\lambda^7}{7!} \\cdot e^{-\\lambda} = \\frac{2^7 \\cdot e^{-2}}{7!} \\approx 0.003437 = P_{1000, 0.002}(7) \\approx P_{\\lambda}(7)\$", "Variant 3\nExtra lineExtra lineExtra lineExtra lineExtra lineExtra lineExtra lineExtra line", "

"), + isMultipleChoice = true ) ) )) + choiceQuizFormDelegate.isEnabled = true +// choiceQuizFormDelegate.setSubmission(Submission( +// reply = Reply(choices = listOf(true, true, false, true)) +// )) + } } \ No newline at end of file From c8af9f89bb5512e7413e0daee05c8160120f5d97 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 15:20:28 +0300 Subject: [PATCH 057/108] add discounting policy --- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 5bcae95127..bd4b95d26f 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -2,6 +2,9 @@ package org.stepik.android.view.step_quiz.ui.delegate import android.widget.TextView import org.stepic.droid.R +import org.stepic.droid.ui.util.changeVisibility +import org.stepik.android.model.DiscountingPolicyType +import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult @@ -51,6 +54,13 @@ class StepQuizDelegate( actionButton.isEnabled = stepQuizFormMapper.isQuizActionEnabled(state) actionButton.text = resolveQuizActionButtonText(state) + + val isNeedShowDiscountingPolicy = + state.restrictions.discountingPolicyType != DiscountingPolicyType.NoDiscount && + (state.submissionState as? StepQuizView.SubmissionState.Loaded)?.submission?.status != Submission.Status.CORRECT + + stepQuizDiscountingPolicy.changeVisibility(isNeedShowDiscountingPolicy) + stepQuizDiscountingPolicy.text = resolveQuizDiscountingPolicyText(state) } private fun resolveQuizActionButtonText(state: StepQuizView.State.AttemptLoaded): String = @@ -74,6 +84,26 @@ class StepQuizDelegate( } } + private fun resolveQuizDiscountingPolicyText(state: StepQuizView.State.AttemptLoaded): String? = + with(state.restrictions) { + when (discountingPolicyType) { + DiscountingPolicyType.Inverse -> + context.getString(R.string.discount_policy_inverse_title) + + DiscountingPolicyType.FirstOne, DiscountingPolicyType.FirstThree -> { + val remainingSubmissionCount = discountingPolicyType.numberOfTries() - state.restrictions.submissionCount + if (remainingSubmissionCount > 0) { + context.resources.getQuantityString(R.plurals.discount_policy_first_n, remainingSubmissionCount, remainingSubmissionCount) + } else { + context.getString(R.string.discount_policy_no_way) + } + } + + else -> + null + } + } + fun syncReplyState() { if (stepQuizFormMapper.isSubmissionInTerminalState(currentState ?: return)) return From f9f08d55d75e4b15f801212dd1c263feaf9b66e7 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 15:23:28 +0300 Subject: [PATCH 058/108] fix number input types --- .../view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 6a67d8a043..5b3f353d20 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -37,7 +37,7 @@ class TextStepQuizFormDelegate( InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE to R.string.step_quiz_string_description AppConstants.TYPE_NUMBER -> - InputType.TYPE_CLASS_NUMBER to R.string.step_quiz_number_description + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED to R.string.step_quiz_number_description AppConstants.TYPE_MATH -> InputType.TYPE_CLASS_TEXT to R.string.step_quiz_math_description From 2dfd199e5b71df75d0748497b60264ef80664273 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 16:48:49 +0300 Subject: [PATCH 059/108] preserve attempt for text quiz --- .../interactor/StepQuizInteractor.kt | 14 ++++++++ .../step_quiz/StepQuizPresenter.kt | 34 +++++++++++++------ .../step_quiz/ui/delegate/StepQuizDelegate.kt | 4 ++- .../ui/fragment/TextStepQuizFragment.kt | 2 +- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt index e366266cf7..fee1de82ed 100644 --- a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt @@ -4,6 +4,7 @@ import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single import org.stepic.droid.persistence.model.StepPersistentWrapper +import org.stepic.droid.util.AppConstants import org.stepic.droid.util.maybeFirst import org.stepik.android.domain.attempt.repository.AttemptRepository import org.stepik.android.domain.lesson.model.LessonData @@ -11,6 +12,7 @@ import org.stepik.android.domain.step_quiz.model.StepQuizRestrictions import org.stepik.android.domain.submission.repository.SubmissionRepository import org.stepik.android.model.DiscountingPolicyType import org.stepik.android.model.Reply +import org.stepik.android.model.Step import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt import java.util.concurrent.TimeUnit @@ -70,4 +72,16 @@ constructor( submissionRepository .getSubmissionsForStep(stepId) .map { it.size } + + fun isNeedRecreateAttemptForNewSubmission(step: Step): Boolean = + when (step.block?.name) { + AppConstants.TYPE_STRING, + AppConstants.TYPE_NUMBER, + AppConstants.TYPE_MATH, + AppConstants.TYPE_FREE_ANSWER -> + false + + else -> + true + } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index 131045267b..d1e4f10a2f 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -11,6 +11,7 @@ import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepik.android.domain.lesson.model.LessonData import org.stepik.android.domain.step_quiz.interactor.StepQuizInteractor import org.stepik.android.model.Reply +import org.stepik.android.model.Step import org.stepik.android.model.Submission import org.stepik.android.presentation.base.PresenterBase import javax.inject.Inject @@ -68,19 +69,30 @@ constructor( .map { StepQuizView.SubmissionState.Loaded(it) as StepQuizView.SubmissionState } .toSingle(StepQuizView.SubmissionState.Empty) - fun createAttempt(stepId: Long) { + fun createAttempt(step: Step) { val oldState = (state as? StepQuizView.State.AttemptLoaded) ?: return - - state = StepQuizView.State.Loading - compositeDisposable += stepQuizInteractor - .createAttempt(stepId) - .subscribeOn(backgroundScheduler) - .observeOn(mainScheduler) - .subscribeBy( - onSuccess = { state = StepQuizView.State.AttemptLoaded(it, StepQuizView.SubmissionState.Empty, oldState.restrictions) }, - onError = { state = StepQuizView.State.NetworkError } - ) + + if (stepQuizInteractor.isNeedRecreateAttemptForNewSubmission(step)) { + state = StepQuizView.State.Loading + + compositeDisposable += stepQuizInteractor + .createAttempt(step.id) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { state = StepQuizView.State.AttemptLoaded(it, StepQuizView.SubmissionState.Empty, oldState.restrictions) }, + onError = { state = StepQuizView.State.NetworkError } + ) + } else { + val submissionState = (oldState.submissionState as? StepQuizView.SubmissionState.Loaded) + ?.submission + ?.let { Submission(attempt = oldState.attempt.id, reply = it.reply, status = Submission.Status.LOCAL) } + ?.let { StepQuizView.SubmissionState.Loaded(it) } + ?: StepQuizView.SubmissionState.Empty + + state = oldState.copy(submissionState = submissionState) + } } fun createSubmission(reply: Reply) { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index bd4b95d26f..d0fded8ef1 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -4,6 +4,7 @@ import android.widget.TextView import org.stepic.droid.R import org.stepic.droid.ui.util.changeVisibility import org.stepik.android.model.DiscountingPolicyType +import org.stepik.android.model.Step import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView @@ -13,6 +14,7 @@ import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizDelegate( + private val step: Step, private val stepQuizFormDelegate: StepQuizFormDelegate, private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, private val actionButton: TextView, @@ -34,7 +36,7 @@ class StepQuizDelegate( val state = currentState ?: return if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { - presenter.createAttempt(state.attempt.step) + presenter.createAttempt(step) } else { when (val replyResult = stepQuizFormDelegate.createReply()) { is ReplyResult.Success -> diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 185f14eeba..81af4cc23f 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -81,7 +81,7 @@ class TextStepQuizFragment : Fragment(), StepQuizView { stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) - stepQuizDelegate = StepQuizDelegate(textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, stepQuizDiscountingPolicy, presenter) + stepQuizDelegate = StepQuizDelegate(stepWrapper.step, textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, stepQuizDiscountingPolicy, presenter) } override fun onStart() { From 724b48292085a70bdfcd9f590f70aabb2f6228c4 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 17:16:58 +0300 Subject: [PATCH 060/108] string res for empty text reply error --- .../view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt | 2 +- app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 5b3f353d20..6b61c0ac65 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -69,7 +69,7 @@ class TextStepQuizFormDelegate( } ReplyResult.Success(reply) } else { - ReplyResult.Error(context.getString(R.string.empty_courses_anonymous)) + ReplyResult.Error(context.getString(R.string.step_quiz_text_empty_reply)) } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fe38c3bcb1..33cc0e1e23 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -729,6 +729,8 @@ Напишите текст (строку) Напишите эссе + Ответ не может быть пустым + Выберите почтовый клиент \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e28d48223..660c771570 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -759,6 +759,8 @@ Write an answer in form of a text (string) Write an essay + An answer cannot be empty + Choose email app From e9f2ca90dfde3a025e60dbda45dcb45b00e30549 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Thu, 4 Jul 2019 19:36:01 +0300 Subject: [PATCH 061/108] fix bottom margin for step quiz container --- .../stepik/android/view/step/ui/fragment/StepFragment.kt | 9 +++++++++ app/src/main/res/values/dimens.xml | 1 + 2 files changed, 10 insertions(+) diff --git a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt index 775aa710ab..0ffc80e3b1 100644 --- a/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step/ui/fragment/StepFragment.kt @@ -188,6 +188,15 @@ class StepFragment : Fragment(), StepView { override fun setNavigation(directions: Set) { stepNavigationDelegate.setState(directions) + stepQuizContainer.layoutParams = (stepQuizContainer.layoutParams as ViewGroup.MarginLayoutParams) + .apply { + bottomMargin = + if (stepNavigation.visibility == View.VISIBLE) { + 0 + } else { + resources.getDimensionPixelSize(R.dimen.step_quiz_container_bottom_margin) + } + } } override fun showLesson(direction: StepNavigationDirection, lessonData: LessonData) { diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9030858d73..72ae5e7559 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -215,6 +215,7 @@ 8dp @dimen/step_content_block_radius + 16dp 8dp @dimen/step_control_block_radius From 8f8cb490fc1819950a72875a9faed4b1f20489da Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 12:58:54 +0300 Subject: [PATCH 062/108] update step progress on correct submission --- .../org/stepic/droid/di/AppCoreComponent.kt | 2 ++ .../interactor/StepQuizInteractor.kt | 12 ++++++- .../ViewAssignmentReportInteractor.kt | 4 +++ .../presentation/lesson/LessonPresenter.kt | 34 +++++++++++++++++++ .../step_quiz/StepQuizPresenter.kt | 8 ++++- .../step_quiz/model/ReplyResult.kt | 4 +-- .../view/base/ui/drawable/GravityDrawable.kt | 1 - .../view/injection/step_quiz/StepQuizBus.kt | 6 ++++ .../injection/step_quiz/StepQuizBusModule.kt | 34 +++++++++++++++++++ .../StepQuizFeedbackBlocksDelegate.kt | 3 +- .../ui/delegate/TextStepQuizFormDelegate.kt | 2 +- 11 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBus.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBusModule.kt diff --git a/app/src/main/java/org/stepic/droid/di/AppCoreComponent.kt b/app/src/main/java/org/stepic/droid/di/AppCoreComponent.kt index d2692fced6..556ad0e6cb 100644 --- a/app/src/main/java/org/stepic/droid/di/AppCoreComponent.kt +++ b/app/src/main/java/org/stepic/droid/di/AppCoreComponent.kt @@ -60,6 +60,7 @@ import org.stepik.android.view.injection.progress.ProgressBusModule import org.stepik.android.view.injection.step.StepComponent import org.stepik.android.view.injection.step.StepDiscussionBusModule import org.stepik.android.view.injection.step_content_video.VideoStepContentComponent +import org.stepik.android.view.injection.step_quiz.StepQuizBusModule import org.stepik.android.view.injection.video_player.VideoPlayerComponent import org.stepik.android.view.notification.service.BootCompleteService import org.stepik.android.view.notification.service.NotificationAlarmService @@ -92,6 +93,7 @@ import org.stepik.android.view.injection.view_assignment.ViewAssignmentComponent ProgressBusModule::class, ViewAssignmentBusModule::class, StepDiscussionBusModule::class, + StepQuizBusModule::class, PersonalDeadlinesDataModule::class, CourseRoutingModule::class, // todo unite it in RoutingModule::class diff --git a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt index fee1de82ed..0280ebedba 100644 --- a/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/step_quiz/interactor/StepQuizInteractor.kt @@ -3,6 +3,7 @@ package org.stepik.android.domain.step_quiz.interactor import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single +import io.reactivex.subjects.PublishSubject import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants import org.stepic.droid.util.maybeFirst @@ -15,12 +16,16 @@ import org.stepik.android.model.Reply import org.stepik.android.model.Step import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt +import org.stepik.android.view.injection.step_quiz.StepQuizBus import java.util.concurrent.TimeUnit import javax.inject.Inject class StepQuizInteractor @Inject constructor( + @StepQuizBus + private val stepQuizPublisher: PublishSubject, + private val attemptRepository: AttemptRepository, private val submissionRepository: SubmissionRepository ) { @@ -40,7 +45,7 @@ constructor( .getSubmissionsForAttempt(attemptId) .maybeFirst() - fun createSubmission(attemptId: Long, reply: Reply): Single = + fun createSubmission(stepId: Long, attemptId: Long, reply: Reply): Single = submissionRepository .createSubmission(Submission(attempt = attemptId, reply = reply)) .flatMapObservable { @@ -50,6 +55,11 @@ constructor( .skipWhile { it.status == Submission.Status.EVALUATION } } .firstOrError() + .doOnSuccess { newSubmission -> + if (newSubmission.status == Submission.Status.CORRECT) { + stepQuizPublisher.onNext(stepId) + } + } fun getStepRestrictions(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Single = getStepSubmissionCount(stepPersistentWrapper.step.id) diff --git a/app/src/main/java/org/stepik/android/domain/view_assignment/interactor/ViewAssignmentReportInteractor.kt b/app/src/main/java/org/stepik/android/domain/view_assignment/interactor/ViewAssignmentReportInteractor.kt index 33a5ac36e2..5dd985818f 100644 --- a/app/src/main/java/org/stepik/android/domain/view_assignment/interactor/ViewAssignmentReportInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/view_assignment/interactor/ViewAssignmentReportInteractor.kt @@ -33,6 +33,10 @@ constructor( @ViewAssignmentBus private val viewAssignmentObserver: BehaviorSubject ) { + fun updatePassedStep(step: Step, assignment: Assignment?): Completable = + updateLocalStepProgress(step, assignment) + .andThen(localProgressInteractor.updateStepsProgress(listOf(step))) + fun reportViewAssignment(step: Step, assignment: Assignment?, unit: Unit?, course: Course?): Completable = updateLocalLastStep(step, unit, course) .andThen(updateLocalStepProgress(step, assignment)) diff --git a/app/src/main/java/org/stepik/android/presentation/lesson/LessonPresenter.kt b/app/src/main/java/org/stepik/android/presentation/lesson/LessonPresenter.kt index 2c93db3155..b359dfc1d6 100644 --- a/app/src/main/java/org/stepik/android/presentation/lesson/LessonPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/lesson/LessonPresenter.kt @@ -24,6 +24,7 @@ import org.stepik.android.domain.step.interactor.StepIndexingInteractor import org.stepik.android.domain.view_assignment.interactor.ViewAssignmentReportInteractor import org.stepik.android.model.Progress import org.stepik.android.presentation.lesson.mapper.LessonStateMapper +import org.stepik.android.view.injection.step_quiz.StepQuizBus import javax.inject.Inject class LessonPresenter @@ -38,6 +39,9 @@ constructor( private val progressObservable: Observable, + @StepQuizBus + private val stepQuizObservable: Observable, + private val stepViewReportInteractor: ViewAssignmentReportInteractor, private val stepIndexingInteractor: StepIndexingInteractor, @@ -61,6 +65,7 @@ constructor( init { subscribeForProgressesUpdates() + subscribeForStepPassedUpdates() } override fun attachView(view: LessonView) { @@ -251,6 +256,35 @@ constructor( )) } + /** + * Step passed + */ + private fun subscribeForStepPassedUpdates() { + compositeDisposable += stepQuizObservable + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy(onNext = ::onStepPassed, onError = emptyOnErrorStub) + } + + private fun onStepPassed(stepId: Long) { + val state = (state as? LessonView.State.LessonLoaded) + ?: return + + val stepsState = (state.stepsState as? LessonView.StepsState.Loaded) + ?: return + + val stepItem = stepsState + .stepItems + .find { it.stepWrapper.step.id == stepId } + ?: return + + compositeDisposable += stepViewReportInteractor + .updatePassedStep(stepItem.stepWrapper.step, stepItem.assignment) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy(onError = emptyOnErrorStub) + } + /** * Indexing */ diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index d1e4f10a2f..c424171a37 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -69,6 +69,9 @@ constructor( .map { StepQuizView.SubmissionState.Loaded(it) as StepQuizView.SubmissionState } .toSingle(StepQuizView.SubmissionState.Empty) + /** + * Attemtps + */ fun createAttempt(step: Step) { val oldState = (state as? StepQuizView.State.AttemptLoaded) ?: return @@ -95,6 +98,9 @@ constructor( } } + /** + * Submissions + */ fun createSubmission(reply: Reply) { val oldState = (state as? StepQuizView.State.AttemptLoaded) ?: return @@ -103,7 +109,7 @@ constructor( state = oldState.copy(submissionState = StepQuizView.SubmissionState.Loaded(submission)) compositeDisposable += stepQuizInteractor - .createSubmission(oldState.attempt.id, reply) + .createSubmission(oldState.attempt.step, oldState.attempt.id, reply) .subscribeOn(backgroundScheduler) .observeOn(mainScheduler) .subscribeBy( diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt index e8ec3d2048..a94ee89b31 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/model/ReplyResult.kt @@ -3,6 +3,6 @@ package org.stepik.android.presentation.step_quiz.model import org.stepik.android.model.Reply sealed class ReplyResult { - data class Success(val reply: Reply): ReplyResult() - data class Error(val message: String): ReplyResult() + data class Success(val reply: Reply) : ReplyResult() + data class Error(val message: String) : ReplyResult() } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt b/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt index 4f63c116c7..299b47c5e7 100644 --- a/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt +++ b/app/src/main/java/org/stepik/android/view/base/ui/drawable/GravityDrawable.kt @@ -1,7 +1,6 @@ package org.stepik.android.view.base.ui.drawable import android.annotation.SuppressLint -import android.content.res.Resources import android.graphics.Canvas import android.graphics.ColorFilter import android.graphics.drawable.Drawable diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBus.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBus.kt new file mode 100644 index 0000000000..9b70ead8df --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBus.kt @@ -0,0 +1,6 @@ +package org.stepik.android.view.injection.step_quiz + +import javax.inject.Qualifier + +@Qualifier +annotation class StepQuizBus \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBusModule.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBusModule.kt new file mode 100644 index 0000000000..e6aa770494 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizBusModule.kt @@ -0,0 +1,34 @@ +package org.stepik.android.view.injection.step_quiz + +import dagger.Module +import dagger.Provides +import io.reactivex.Observable +import io.reactivex.Scheduler +import io.reactivex.subjects.PublishSubject +import org.stepic.droid.di.AppSingleton +import org.stepic.droid.di.qualifiers.BackgroundScheduler + +@Module +abstract class StepQuizBusModule { + @Module + companion object { + @Provides + @JvmStatic + @AppSingleton + @StepQuizBus + internal fun provideStepQuizPublisher(): PublishSubject = + PublishSubject.create() + + @Provides + @JvmStatic + @AppSingleton + @StepQuizBus + internal fun provideStepQuizObservable( + @StepQuizBus + stepQuizPublisher: PublishSubject, + @BackgroundScheduler + scheduler: Scheduler + ): Observable = + stepQuizPublisher.observeOn(scheduler) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt index d2e2850214..452f0a4906 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt @@ -80,7 +80,8 @@ class StepQuizFeedbackBlocksDelegate( private fun setHint( targetView: TextView, - @DrawableRes backgroundRes: Int, @DrawableRes hintedBackgroundRes: Int, + @DrawableRes backgroundRes: Int, + @DrawableRes hintedBackgroundRes: Int, hint: String? ) { if (hint != null) { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index 6b61c0ac65..ae80e029af 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -81,7 +81,7 @@ class TextStepQuizFormDelegate( textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) textField.text = - when(stepWrapper.step.block?.name) { + when (stepWrapper.step.block?.name) { AppConstants.TYPE_NUMBER -> reply?.number From 873424b6b29006f8b3c41b40bd7e07c6164e902b Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 15:32:24 +0300 Subject: [PATCH 063/108] exclude common quiz layout --- .../StepQuizFeedbackBlocksDelegate.kt | 8 ++-- .../ui/delegate/TextStepQuizFormDelegate.kt | 9 ++-- .../ui/fragment/TextStepQuizFragment.kt | 14 +++--- ...p_quiz_text.xml => fragment_step_quiz.xml} | 44 +++---------------- .../main/res/layout/layout_step_quiz_text.xml | 37 ++++++++++++++++ .../layout/view_step_quiz_submit_button.xml | 2 +- 6 files changed, 61 insertions(+), 53 deletions(-) rename app/src/main/res/layout/{fragment_step_quiz_text.xml => fragment_step_quiz.xml} (70%) create mode 100644 app/src/main/res/layout/layout_step_quiz_text.xml diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt index 452f0a4906..0d4ebe0281 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt @@ -29,10 +29,10 @@ class StepQuizFeedbackBlocksDelegate( init { viewStateDelegate.addState() - viewStateDelegate.addState(stepQuizFeedbackEvaluation) - viewStateDelegate.addState(stepQuizFeedbackCorrect, stepQuizFeedbackHint) - viewStateDelegate.addState(stepQuizFeedbackWrong, stepQuizFeedbackHint) - viewStateDelegate.addState(stepQuizFeedbackValidation) + viewStateDelegate.addState(containerView, stepQuizFeedbackEvaluation) + viewStateDelegate.addState(containerView, stepQuizFeedbackCorrect, stepQuizFeedbackHint) + viewStateDelegate.addState(containerView, stepQuizFeedbackWrong, stepQuizFeedbackHint) + viewStateDelegate.addState(containerView, stepQuizFeedbackValidation) val drawable = AppCompatResources.getDrawable(context, R.drawable.ic_step_quiz_evaluation) as? AnimationDrawable stepQuizFeedbackEvaluation.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index ae80e029af..e3319d37cb 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -7,7 +7,8 @@ import android.text.InputType import android.view.Gravity import android.view.View import android.widget.TextView -import kotlinx.android.synthetic.main.fragment_step_quiz_text.view.* +import kotlinx.android.synthetic.main.fragment_step_quiz.view.* +import kotlinx.android.synthetic.main.layout_step_quiz_text.view.* import org.stepic.droid.R import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.AppConstants @@ -20,15 +21,15 @@ import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate class TextStepQuizFormDelegate( - private val stepWrapper: StepPersistentWrapper, - containerView: View + containerView: View, + private val stepWrapper: StepPersistentWrapper ) : StepQuizFormDelegate { private val context = containerView.context private val stepQuizFormMapper = StepQuizFormMapper() private val textField = containerView.stringStepQuizField as TextView - private val quizDescription = containerView.stringStepQuizDescription + private val quizDescription = containerView.stepQuizDescription init { val (inputType, textRes) = diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 81af4cc23f..7c49381a32 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -10,7 +10,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.error_no_connection_with_button_small.view.* -import kotlinx.android.synthetic.main.fragment_step_quiz_text.* +import kotlinx.android.synthetic.main.fragment_step_quiz.* +import kotlinx.android.synthetic.main.layout_step_quiz_text.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.base.App @@ -66,7 +67,10 @@ class TextStepQuizFragment : Fragment(), StepQuizView { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = - inflater.inflate(R.layout.fragment_step_quiz_text, container, false) + (inflater.inflate(R.layout.fragment_step_quiz, container, false) as ViewGroup) + .apply { + addView(inflater.inflate(R.layout.layout_step_quiz_text, this, false)) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -74,14 +78,14 @@ class TextStepQuizFragment : Fragment(), StepQuizView { viewStateDelegate = ViewStateDelegate() viewStateDelegate.addState() viewStateDelegate.addState(stepQuizProgress) - viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, stringStepQuizField, stringStepQuizDescription, stepQuizSubmit) + viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, stringStepQuizField, stepQuizDescription, stepQuizAction) viewStateDelegate.addState(stepQuizNetworkError) stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) - textStepQuizFormDelegate = TextStepQuizFormDelegate(stepWrapper, view) - stepQuizDelegate = StepQuizDelegate(stepWrapper.step, textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizSubmit, stepQuizDiscountingPolicy, presenter) + textStepQuizFormDelegate = TextStepQuizFormDelegate(view, stepWrapper) + stepQuizDelegate = StepQuizDelegate(stepWrapper.step, textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizAction, stepQuizDiscountingPolicy, presenter) } override fun onStart() { diff --git a/app/src/main/res/layout/fragment_step_quiz_text.xml b/app/src/main/res/layout/fragment_step_quiz.xml similarity index 70% rename from app/src/main/res/layout/fragment_step_quiz_text.xml rename to app/src/main/res/layout/fragment_step_quiz.xml index 72da521b5c..3559e702a3 100644 --- a/app/src/main/res/layout/fragment_step_quiz_text.xml +++ b/app/src/main/res/layout/fragment_step_quiz.xml @@ -24,7 +24,7 @@ tools:text="@string/discount_policy_inverse_title" /> - - + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/view_step_quiz_submit_button.xml b/app/src/main/res/layout/view_step_quiz_submit_button.xml index cca246d6d7..af28b8583e 100644 --- a/app/src/main/res/layout/view_step_quiz_submit_button.xml +++ b/app/src/main/res/layout/view_step_quiz_submit_button.xml @@ -1,7 +1,7 @@ Date: Fri, 5 Jul 2019 15:42:02 +0300 Subject: [PATCH 064/108] fix showNetworkError --- .../android/presentation/step_quiz/StepQuizPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index c424171a37..e398afbfc3 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -70,7 +70,7 @@ constructor( .toSingle(StepQuizView.SubmissionState.Empty) /** - * Attemtps + * Attempts */ fun createAttempt(step: Step) { val oldState = (state as? StepQuizView.State.AttemptLoaded) @@ -120,7 +120,7 @@ constructor( restrictions = oldState.restrictions.copy(submissionCount = oldState.restrictions.submissionCount + 1) ) }, - onError = { state = oldState } + onError = { state = oldState; view?.showNetworkError() } ) } From 00ac80c2e966bcf6f9b6541d8db02e63b0ad3a3d Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 15:50:22 +0300 Subject: [PATCH 065/108] remove unused steps fragments --- .../ui/fragments/FreeResponseStepFragment.kt | 56 ------------------- .../droid/ui/fragments/MathStepFragment.kt | 14 ----- .../droid/ui/fragments/NumberStepFragment.kt | 22 -------- .../fragments/SingleLineSendStepFragment.java | 48 ---------------- .../droid/ui/fragments/StringStepFragment.kt | 40 ------------- .../util/resolvers/StepTypeResolverImpl.java | 13 +---- .../res/layout/view_free_answer_attempt.xml | 2 +- .../res/layout/view_single_line_attempt.xml | 13 ----- app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 10 files changed, 2 insertions(+), 208 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/ui/fragments/FreeResponseStepFragment.kt delete mode 100644 app/src/main/java/org/stepic/droid/ui/fragments/MathStepFragment.kt delete mode 100644 app/src/main/java/org/stepic/droid/ui/fragments/NumberStepFragment.kt delete mode 100644 app/src/main/java/org/stepic/droid/ui/fragments/SingleLineSendStepFragment.java delete mode 100644 app/src/main/java/org/stepic/droid/ui/fragments/StringStepFragment.kt delete mode 100644 app/src/main/res/layout/view_single_line_attempt.xml diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/FreeResponseStepFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/FreeResponseStepFragment.kt deleted file mode 100644 index 2b49995d4a..0000000000 --- a/app/src/main/java/org/stepic/droid/ui/fragments/FreeResponseStepFragment.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.stepic.droid.ui.fragments - -import android.os.Bundle -import android.view.View -import android.widget.EditText -import org.stepic.droid.R -import org.stepik.android.model.attempts.Attempt -import org.stepik.android.model.Reply - -class FreeResponseStepFragment: StepAttemptFragment() { - - lateinit var answerField: EditText - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - answerField = layoutInflater.inflate(R.layout.view_free_answer_attempt, attemptContainer, false) as EditText - attemptContainer.addView(answerField) - } - - override fun showAttempt(attempt: Attempt) { - //do nothing, because this attempt doesn't have any specific. - answerField.text.clear() - } - - override fun generateReply(): Reply { - var answer = answerField.text.toString() - if (attempt?.dataset?.isHtmlEnabled == true) { - answer = textResolver.replaceWhitespaceToBr(answer) - } - - return Reply(text = answer, attachments = emptyList()) - } - - override fun blockUIBeforeSubmit(needBlock: Boolean) { - answerField.isEnabled = !needBlock - } - - override fun onRestoreSubmission() { - val reply = submission.reply ?: return - - val text = reply.text - if (attempt?.dataset?.isHtmlEnabled == true) { - //todo show as html in enhanced latexview - answerField.setText(textResolver.fromHtml(text)) - } else { - answerField.setText(text) - } - } - - override fun getCorrectString(): String = getString(R.string.correct_free_response) - - override fun onPause() { - super.onPause() - answerField.clearFocus() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/MathStepFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/MathStepFragment.kt deleted file mode 100644 index 4be32ed76d..0000000000 --- a/app/src/main/java/org/stepic/droid/ui/fragments/MathStepFragment.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.stepic.droid.ui.fragments - -import org.stepik.android.model.Reply - -class MathStepFragment : SingleLineSendStepFragment() { - - override fun generateReply(): Reply = Reply(formula = answerField.text.toString()) - - override fun onRestoreSubmission() { - val formula = submission.reply?.formula ?: return - answerField.setText(formula) - } - -} diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/NumberStepFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/NumberStepFragment.kt deleted file mode 100644 index 509025d4da..0000000000 --- a/app/src/main/java/org/stepic/droid/ui/fragments/NumberStepFragment.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.stepic.droid.ui.fragments - -import android.os.Bundle -import android.text.InputType -import android.view.View -import org.stepik.android.model.Reply - -class NumberStepFragment: SingleLineSendStepFragment() { - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - answerField.setRawInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL) - } - - override fun generateReply(): Reply = - Reply(number = answerField.text.toString()) - - override fun onRestoreSubmission() { - val text = submission.reply?.number ?: return - answerField.setText(text) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/SingleLineSendStepFragment.java b/app/src/main/java/org/stepic/droid/ui/fragments/SingleLineSendStepFragment.java deleted file mode 100644 index 4749264fe8..0000000000 --- a/app/src/main/java/org/stepic/droid/ui/fragments/SingleLineSendStepFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.stepic.droid.ui.fragments; - -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.TextView; - -import org.stepic.droid.R; -import org.stepik.android.model.attempts.Attempt; - -public abstract class SingleLineSendStepFragment extends StepAttemptFragment { - - protected EditText answerField; - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - answerField = (EditText) ((LayoutInflater) this.getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.view_single_line_attempt, attemptContainer, false); - attemptContainer.addView(answerField); - answerField.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - boolean handled = false; - if (actionId == EditorInfo.IME_ACTION_SEND) { - actionButton.performClick(); - handled = true; - } - return handled; - } - }); - } - - @Override - protected final void showAttempt(Attempt attempt) { - answerField.getText().clear(); - } - - @Override - protected final void blockUIBeforeSubmit(boolean needBlock) { - answerField.setEnabled(!needBlock); - } - -} diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/StringStepFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/StringStepFragment.kt deleted file mode 100644 index 54f1547b05..0000000000 --- a/app/src/main/java/org/stepic/droid/ui/fragments/StringStepFragment.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.stepic.droid.ui.fragments - -import android.os.Bundle -import android.view.View -import android.widget.EditText -import org.stepic.droid.R -import org.stepik.android.model.attempts.Attempt -import org.stepik.android.model.Reply - -class StringStepFragment: StepAttemptFragment() { - - private lateinit var answerField: EditText - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - answerField = layoutInflater.inflate(R.layout.view_free_answer_attempt, attemptContainer, false) as EditText - attemptContainer.addView(answerField) - } - - override fun showAttempt(attempt: Attempt) { - answerField.text.clear() - } - - override fun blockUIBeforeSubmit(needBlock: Boolean) { - answerField.isEnabled = !needBlock - } - - override fun generateReply(): Reply = - Reply(text = answerField.text.toString()) - - override fun onRestoreSubmission() { - val text = submission.reply?.text ?: return - answerField.setText(text) - } - - override fun onPause() { - super.onPause() - answerField.clearFocus() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java index c073acaf3f..29607d0bea 100644 --- a/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java +++ b/app/src/main/java/org/stepic/droid/util/resolvers/StepTypeResolverImpl.java @@ -10,15 +10,11 @@ import org.stepic.droid.ui.fragments.ChoiceStepFragment; import org.stepic.droid.ui.fragments.CodeStepFragment; import org.stepic.droid.ui.fragments.FillBlanksFragment; -import org.stepic.droid.ui.fragments.FreeResponseStepFragment; import org.stepic.droid.ui.fragments.MatchingStepFragment; -import org.stepic.droid.ui.fragments.MathStepFragment; import org.stepic.droid.ui.fragments.NotSupportedYetStepFragment; -import org.stepic.droid.ui.fragments.NumberStepFragment; import org.stepic.droid.ui.fragments.PyCharmStepFragment; import org.stepic.droid.ui.fragments.SortingStepFragment; import org.stepic.droid.ui.fragments.SqlStepFragment; -import org.stepic.droid.ui.fragments.StringStepFragment; import org.stepic.droid.ui.fragments.TableChoiceStepFragment; import org.stepic.droid.ui.quiz.ChoiceQuizDelegate; import org.stepic.droid.ui.quiz.NotSupportedQuizDelegate; @@ -112,14 +108,6 @@ public StepBaseFragment getFragment(Step step) { switch (type) { case AppConstants.TYPE_CHOICE: return new ChoiceStepFragment(); - case AppConstants.TYPE_FREE_ANSWER: - return new FreeResponseStepFragment(); - case AppConstants.TYPE_STRING: - return new StringStepFragment(); - case AppConstants.TYPE_MATH: - return new MathStepFragment(); - case AppConstants.TYPE_NUMBER: - return new NumberStepFragment(); case AppConstants.TYPE_PYCHARM: return new PyCharmStepFragment(); case AppConstants.TYPE_SORTING: @@ -172,6 +160,7 @@ public boolean isNeedUseOldStepContainer(@NotNull Step step) { switch (step.getBlock().getName()) { case AppConstants.TYPE_TEXT: case AppConstants.TYPE_VIDEO: + case AppConstants.TYPE_STRING: case AppConstants.TYPE_NUMBER: case AppConstants.TYPE_MATH: diff --git a/app/src/main/res/layout/view_free_answer_attempt.xml b/app/src/main/res/layout/view_free_answer_attempt.xml index 10c7caf9a3..b6d76c851b 100644 --- a/app/src/main/res/layout/view_free_answer_attempt.xml +++ b/app/src/main/res/layout/view_free_answer_attempt.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="top|start" - android:hint="@string/hint_for_type_answer" + android:hint="@string/step_quiz_text_field_hint" android:imeOptions="flagNoFullscreen" android:inputType="textMultiLine|textCapSentences" android:maxLines="9000" diff --git a/app/src/main/res/layout/view_single_line_attempt.xml b/app/src/main/res/layout/view_single_line_attempt.xml deleted file mode 100644 index 1571fd15f9..0000000000 --- a/app/src/main/res/layout/view_single_line_attempt.xml +++ /dev/null @@ -1,13 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 33cc0e1e23..965fbeed54 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -76,7 +76,6 @@ включите интернет-соединение.
Ваше решение сохранено. - Введите ответ здесь… PyCharm Educational Edition
]]> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 660c771570..bea33fe4b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,7 +92,6 @@ completing the assignments. Your answer was saved. - Type your answer here… PyCharm Educational Edition]]> This is a peer-review assignment. Tap to open in web and receive From 5f1701abeebed192a9f89477c849c5eb60b17ace Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 16:31:01 +0300 Subject: [PATCH 066/108] fix naming & optimisations --- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 20 ++++++++++--------- .../ui/fragment/TextStepQuizFragment.kt | 17 +++++++++------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index d0fded8ef1..9816e59b0d 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -17,11 +17,13 @@ class StepQuizDelegate( private val step: Step, private val stepQuizFormDelegate: StepQuizFormDelegate, private val stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate, - private val actionButton: TextView, + + private val stepQuizActionButton: TextView, private val stepQuizDiscountingPolicy: TextView, - private val presenter: StepQuizPresenter + + private val stepQuizPresenter: StepQuizPresenter ) { - private val context = actionButton.context + private val context = stepQuizActionButton.context private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() private val stepQuizFormMapper = StepQuizFormMapper() @@ -29,18 +31,18 @@ class StepQuizDelegate( private var currentState: StepQuizView.State.AttemptLoaded? = null init { - actionButton.setOnClickListener { onActionButtonClicked() } + stepQuizActionButton.setOnClickListener { onActionButtonClicked() } } private fun onActionButtonClicked() { val state = currentState ?: return if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { - presenter.createAttempt(step) + stepQuizPresenter.createAttempt(step) } else { when (val replyResult = stepQuizFormDelegate.createReply()) { is ReplyResult.Success -> - presenter.createSubmission(replyResult.reply) + stepQuizPresenter.createSubmission(replyResult.reply) is ReplyResult.Error -> stepQuizFeedbackBlocksDelegate.setState(StepQuizFeedbackState.Validation(replyResult.message)) @@ -54,8 +56,8 @@ class StepQuizDelegate( stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) stepQuizFormDelegate.setState(state) - actionButton.isEnabled = stepQuizFormMapper.isQuizActionEnabled(state) - actionButton.text = resolveQuizActionButtonText(state) + stepQuizActionButton.isEnabled = stepQuizFormMapper.isQuizActionEnabled(state) + stepQuizActionButton.text = resolveQuizActionButtonText(state) val isNeedShowDiscountingPolicy = state.restrictions.discountingPolicyType != DiscountingPolicyType.NoDiscount && @@ -113,6 +115,6 @@ class StepQuizDelegate( ?.reply ?: return - presenter.syncReplyState(reply) + stepQuizPresenter.syncReplyState(reply) } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index 7c49381a32..ea1bbebb64 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -45,11 +45,8 @@ class TextStepQuizFragment : Fragment(), StepQuizView { private var lessonData: LessonData by argument() private var stepWrapper: StepPersistentWrapper by argument() - private lateinit var stepQuizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate - private lateinit var textStepQuizFormDelegate: TextStepQuizFormDelegate - private lateinit var stepQuizDelegate: StepQuizDelegate - private lateinit var viewStateDelegate: ViewStateDelegate + private lateinit var stepQuizDelegate: StepQuizDelegate override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -83,9 +80,15 @@ class TextStepQuizFragment : Fragment(), StepQuizView { stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } - stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks) - textStepQuizFormDelegate = TextStepQuizFormDelegate(view, stepWrapper) - stepQuizDelegate = StepQuizDelegate(stepWrapper.step, textStepQuizFormDelegate, stepQuizFeedbackBlocksDelegate, stepQuizAction, stepQuizDiscountingPolicy, presenter) + stepQuizDelegate = + StepQuizDelegate( + step = stepWrapper.step, + stepQuizFormDelegate = TextStepQuizFormDelegate(view, stepWrapper), + stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks), + stepQuizActionButton = stepQuizAction, + stepQuizDiscountingPolicy = stepQuizDiscountingPolicy, + stepQuizPresenter = presenter + ) } override fun onStart() { From aea4e780192166d781fb008fd5ec37085c665689 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 17:35:38 +0300 Subject: [PATCH 067/108] clean up --- .../step_quiz/StepQuizPresenter.kt | 3 +-- .../attempt/AttemptRemoteDataSourceImpl.kt | 5 ++-- .../step_quiz/StepQuizPresentationModule.kt | 2 +- .../StepQuizFormResolver.kt} | 4 +-- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 11 ++++---- .../ui/delegate/TextStepQuizFormDelegate.kt | 25 +++++++++---------- 6 files changed, 23 insertions(+), 27 deletions(-) rename app/src/main/java/org/stepik/android/view/step_quiz/{mapper/StepQuizFormMapper.kt => resolver/StepQuizFormResolver.kt} (92%) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt index e398afbfc3..d50a6eca1b 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz/StepQuizPresenter.kt @@ -26,8 +26,7 @@ constructor( @MainScheduler private val mainScheduler: Scheduler ) : PresenterBase() { - private var state: StepQuizView.State = - StepQuizView.State.Idle + private var state: StepQuizView.State = StepQuizView.State.Idle set(value) { field = value view?.setState(value) diff --git a/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt index 14b2166e8b..d364324d78 100644 --- a/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/remote/attempt/AttemptRemoteDataSourceImpl.kt @@ -2,7 +2,7 @@ package org.stepik.android.remote.attempt import io.reactivex.Single import io.reactivex.functions.Function -import org.stepic.droid.util.maybeFirst +import org.stepic.droid.util.first import org.stepic.droid.web.Api import org.stepik.android.data.attempt.source.AttemptRemoteDataSource import org.stepik.android.model.attempts.Attempt @@ -19,8 +19,7 @@ constructor( override fun createAttemptForStep(stepId: Long): Single = api.createNewAttemptReactive(stepId) .map(attemptMapper) - .maybeFirst() - .toSingle() + .first() override fun getAttemptsForStep(stepId: Long): Single> = api.getExistingAttemptsReactive(stepId) diff --git a/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt index e95762f84a..24fcf3e531 100644 --- a/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt +++ b/app/src/main/java/org/stepik/android/view/injection/step_quiz/StepQuizPresentationModule.kt @@ -15,5 +15,5 @@ abstract class StepQuizPresentationModule { @Binds @IntoMap @ViewModelKey(StepQuizPresenter::class) - internal abstract fun bindStepQuizPresenter(textStepQuizPresenter: StepQuizPresenter): ViewModel + internal abstract fun bindStepQuizPresenter(stepQuizPresenter: StepQuizPresenter): ViewModel } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/resolver/StepQuizFormResolver.kt similarity index 92% rename from app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt rename to app/src/main/java/org/stepik/android/view/step_quiz/resolver/StepQuizFormResolver.kt index 3677bb13c7..8697c67c1a 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFormMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/resolver/StepQuizFormResolver.kt @@ -1,9 +1,9 @@ -package org.stepik.android.view.step_quiz.mapper +package org.stepik.android.view.step_quiz.resolver import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView -class StepQuizFormMapper { +object StepQuizFormResolver { fun isQuizEnabled(state: StepQuizView.State.AttemptLoaded): Boolean = state.submissionState is StepQuizView.SubmissionState.Empty || state.submissionState is StepQuizView.SubmissionState.Loaded && diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index 9816e59b0d..f6af78ed1c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -10,7 +10,7 @@ import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.view.step_quiz.mapper.StepQuizFeedbackMapper -import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper +import org.stepik.android.view.step_quiz.resolver.StepQuizFormResolver import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizDelegate( @@ -26,7 +26,6 @@ class StepQuizDelegate( private val context = stepQuizActionButton.context private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() - private val stepQuizFormMapper = StepQuizFormMapper() private var currentState: StepQuizView.State.AttemptLoaded? = null @@ -37,7 +36,7 @@ class StepQuizDelegate( private fun onActionButtonClicked() { val state = currentState ?: return - if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { + if (StepQuizFormResolver.isSubmissionInTerminalState(state)) { stepQuizPresenter.createAttempt(step) } else { when (val replyResult = stepQuizFormDelegate.createReply()) { @@ -56,7 +55,7 @@ class StepQuizDelegate( stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) stepQuizFormDelegate.setState(state) - stepQuizActionButton.isEnabled = stepQuizFormMapper.isQuizActionEnabled(state) + stepQuizActionButton.isEnabled = StepQuizFormResolver.isQuizActionEnabled(state) stepQuizActionButton.text = resolveQuizActionButtonText(state) val isNeedShowDiscountingPolicy = @@ -69,7 +68,7 @@ class StepQuizDelegate( private fun resolveQuizActionButtonText(state: StepQuizView.State.AttemptLoaded): String = with(state.restrictions) { - if (stepQuizFormMapper.isSubmissionInTerminalState(state)) { + if (StepQuizFormResolver.isSubmissionInTerminalState(state)) { if (maxSubmissionCount in 0 until submissionCount) { context.getString(R.string.step_quiz_action_button_no_submissions) } else { @@ -109,7 +108,7 @@ class StepQuizDelegate( } fun syncReplyState() { - if (stepQuizFormMapper.isSubmissionInTerminalState(currentState ?: return)) return + if (StepQuizFormResolver.isSubmissionInTerminalState(currentState ?: return)) return val reply = (stepQuizFormDelegate.createReply() as? ReplyResult.Success) ?.reply diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt index e3319d37cb..46a09868c8 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/delegate/TextStepQuizFormDelegate.kt @@ -1,6 +1,7 @@ package org.stepik.android.view.step_quiz_text.ui.delegate import android.support.annotation.DrawableRes +import android.support.annotation.StringRes import android.support.v4.widget.TextViewCompat import android.support.v7.content.res.AppCompatResources import android.text.InputType @@ -17,7 +18,7 @@ import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.view.base.ui.drawable.GravityDrawable -import org.stepik.android.view.step_quiz.mapper.StepQuizFormMapper +import org.stepik.android.view.step_quiz.resolver.StepQuizFormResolver import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate class TextStepQuizFormDelegate( @@ -26,13 +27,11 @@ class TextStepQuizFormDelegate( ) : StepQuizFormDelegate { private val context = containerView.context - private val stepQuizFormMapper = StepQuizFormMapper() - - private val textField = containerView.stringStepQuizField as TextView + private val quizTextField = containerView.stringStepQuizField as TextView private val quizDescription = containerView.stepQuizDescription init { - val (inputType, textRes) = + val (inputType, @StringRes descriptionTextRes) = when (val blockName = stepWrapper.step.block?.name) { AppConstants.TYPE_STRING -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE to R.string.step_quiz_string_description @@ -50,12 +49,12 @@ class TextStepQuizFormDelegate( throw IllegalArgumentException("Unsupported block type = $blockName") } - textField.inputType = inputType - quizDescription.setText(textRes) + quizTextField.inputType = inputType + quizDescription.setText(descriptionTextRes) } override fun createReply(): ReplyResult = - textField.text.toString().let { value -> + quizTextField.text.toString().let { value -> if (value.isNotEmpty()) { val reply = when (stepWrapper.step.block?.name) { @@ -80,8 +79,8 @@ class TextStepQuizFormDelegate( val reply = submission?.reply - textField.isEnabled = stepQuizFormMapper.isQuizEnabled(state) - textField.text = + quizTextField.isEnabled = StepQuizFormResolver.isQuizEnabled(state) + quizTextField.text = when (stepWrapper.step.block?.name) { AppConstants.TYPE_NUMBER -> reply?.number @@ -107,9 +106,9 @@ class TextStepQuizFormDelegate( } val drawable = drawableRes - ?.let { AppCompatResources.getDrawable(textField.context, it) } - ?.let { GravityDrawable(it, Gravity.BOTTOM, textField.resources.getDimensionPixelSize(R.dimen.step_quiz_text_field_min_height)) } + ?.let { AppCompatResources.getDrawable(quizTextField.context, it) } + ?.let { GravityDrawable(it, Gravity.BOTTOM, quizTextField.resources.getDimensionPixelSize(R.dimen.step_quiz_text_field_min_height)) } - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textField, null, null, drawable, null) + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(quizTextField, null, null, drawable, null) } } \ No newline at end of file From dcfd26260a76a23d6449a9ed21ccf6395c9c8a8f Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 18:16:54 +0300 Subject: [PATCH 068/108] add mono font to fonts provider --- app/src/main/java/org/stepic/droid/fonts/FontType.kt | 4 +++- app/src/main/java/org/stepic/droid/fonts/FontsProviderImpl.kt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepic/droid/fonts/FontType.kt b/app/src/main/java/org/stepic/droid/fonts/FontType.kt index c7deececd3..a2ebb672e7 100644 --- a/app/src/main/java/org/stepic/droid/fonts/FontType.kt +++ b/app/src/main/java/org/stepic/droid/fonts/FontType.kt @@ -1,5 +1,7 @@ package org.stepic.droid.fonts enum class FontType { - regular, italic, bold, boldItalic, medium, light + regular, italic, bold, boldItalic, medium, light, + + mono } diff --git a/app/src/main/java/org/stepic/droid/fonts/FontsProviderImpl.kt b/app/src/main/java/org/stepic/droid/fonts/FontsProviderImpl.kt index 4b73b59cce..3f5668e712 100644 --- a/app/src/main/java/org/stepic/droid/fonts/FontsProviderImpl.kt +++ b/app/src/main/java/org/stepic/droid/fonts/FontsProviderImpl.kt @@ -12,5 +12,6 @@ class FontsProviderImpl @Inject constructor() : FontsProvider { FontType.boldItalic -> "fonts/Roboto-BoldItalic.ttf" FontType.medium -> "fonts/Roboto-Medium.ttf" FontType.light -> "fonts/Roboto-Light.ttf" + FontType.mono -> "fonts/PT-Mono.ttf" } } From 3aa922fdbe3e4214c1b2ac411fb8041a2415cb03 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 18:17:20 +0300 Subject: [PATCH 069/108] use latex expandable layout for hint --- .../main/java/org/stepic/droid/util/HtmlHelper.java | 2 +- .../ui/delegate/StepQuizFeedbackBlocksDelegate.kt | 10 ++++++++-- .../step_quiz_text/ui/fragment/TextStepQuizFragment.kt | 6 +++++- .../res/layout/layout_step_quiz_feedback_block.xml | 4 +--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/util/HtmlHelper.java b/app/src/main/java/org/stepic/droid/util/HtmlHelper.java index 69d184ab95..0e12fac4c5 100644 --- a/app/src/main/java/org/stepic/droid/util/HtmlHelper.java +++ b/app/src/main/java/org/stepic/droid/util/HtmlHelper.java @@ -204,7 +204,7 @@ public static String buildPageWithAdjustingTextAndImage(CharSequence body, @Colo } public static String buildPageWithCustomFont(CharSequence body, String fontPath, @ColorInt int textColorHighlight, int widthPx, String baseUrl) { - return buildPage(body, CollectionsKt.mutableListOf(), fontPath, textColorHighlight, widthPx, baseUrl); + return buildPage(body, CollectionsKt.mutableListOf(MathJaxScript), fontPath, textColorHighlight, widthPx, baseUrl); } public static final String HORIZONTAL_SCROLL_LISTENER = "scrollListener"; diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt index 0d4ebe0281..31189c837f 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt @@ -8,12 +8,15 @@ import android.view.View import android.widget.TextView import kotlinx.android.synthetic.main.layout_step_quiz_feedback_block.view.* import org.stepic.droid.R +import org.stepic.droid.fonts.FontType +import org.stepic.droid.fonts.FontsProvider import org.stepic.droid.ui.util.setCompoundDrawables import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState import org.stepik.android.view.ui.delegate.ViewStateDelegate class StepQuizFeedbackBlocksDelegate( - containerView: View + containerView: View, + private val fontsProvider: FontsProvider ) { private val context = containerView.context private val resources = containerView.resources @@ -46,6 +49,9 @@ class StepQuizFeedbackBlocksDelegate( stepQuizFeedbackValidation.setCompoundDrawables(start = R.drawable.ic_step_quiz_validation) stepQuizFeedbackValidation.setText(R.string.step_quiz_feedback_validation_fill_blanks) + + stepQuizFeedbackHint.setTextSize(14f) + stepQuizFeedbackHint.setBackgroundResource(R.drawable.bg_step_quiz_hint) } fun setState(state: StepQuizFeedbackState) { @@ -86,7 +92,7 @@ class StepQuizFeedbackBlocksDelegate( ) { if (hint != null) { targetView.setBackgroundResource(hintedBackgroundRes) - stepQuizFeedbackHint.text = hint + stepQuizFeedbackHint.setPlainOrLaTeXTextWithCustomFontColored(hint, fontsProvider.provideFontPath(FontType.mono), R.color.new_accent_color, true) stepQuizFeedbackHint.visibility = View.VISIBLE } else { targetView.setBackgroundResource(backgroundRes) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt index ea1bbebb64..6e9a90a634 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_text/ui/fragment/TextStepQuizFragment.kt @@ -15,6 +15,7 @@ import kotlinx.android.synthetic.main.layout_step_quiz_text.* import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R import org.stepic.droid.base.App +import org.stepic.droid.fonts.FontsProvider import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument import org.stepic.droid.util.setTextColor @@ -40,6 +41,9 @@ class TextStepQuizFragment : Fragment(), StepQuizView { @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject + internal lateinit var fontsProvider: FontsProvider + private lateinit var presenter: StepQuizPresenter private var lessonData: LessonData by argument() @@ -84,7 +88,7 @@ class TextStepQuizFragment : Fragment(), StepQuizView { StepQuizDelegate( step = stepWrapper.step, stepQuizFormDelegate = TextStepQuizFormDelegate(view, stepWrapper), - stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks), + stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks, fontsProvider), stepQuizActionButton = stepQuizAction, stepQuizDiscountingPolicy = stepQuizDiscountingPolicy, stepQuizPresenter = presenter diff --git a/app/src/main/res/layout/layout_step_quiz_feedback_block.xml b/app/src/main/res/layout/layout_step_quiz_feedback_block.xml index 5f0cace202..151387940e 100644 --- a/app/src/main/res/layout/layout_step_quiz_feedback_block.xml +++ b/app/src/main/res/layout/layout_step_quiz_feedback_block.xml @@ -46,12 +46,10 @@ tools:drawableStart="@drawable/ic_step_quiz_validation" /> - Date: Fri, 5 Jul 2019 18:58:25 +0300 Subject: [PATCH 070/108] remove redundant attributes --- app/src/main/res/layout/layout_step_quiz_feedback_block.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/res/layout/layout_step_quiz_feedback_block.xml b/app/src/main/res/layout/layout_step_quiz_feedback_block.xml index 151387940e..497bf2ccd5 100644 --- a/app/src/main/res/layout/layout_step_quiz_feedback_block.xml +++ b/app/src/main/res/layout/layout_step_quiz_feedback_block.xml @@ -50,10 +50,6 @@ android:id="@+id/stepQuizFeedbackHint" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="@color/new_accent_color" - android:lineSpacingExtra="4sp" - android:padding="16dp" - tools:text="Variant 1 and Variant 4 are opposite options.
It can’t be the correct choice." /> + android:padding="16dp" /> \ No newline at end of file From 5519e05139a1b53de1da1f66107182028193cc76 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 19:49:35 +0300 Subject: [PATCH 071/108] fix comments models --- .../org/stepic/droid/core/CommentManager.kt | 10 +- .../ui/dialogs/DeleteCommentDialogFragment.kt | 8 +- .../droid/ui/fragments/NewCommentFragment.kt | 8 +- .../main/java/org/stepic/droid/web/Api.java | 9 +- .../java/org/stepic/droid/web/ApiImpl.java | 10 +- .../org/stepic/droid/web/CommentRequest.kt | 7 - .../org/stepic/droid/web/CommentsResponse.kt | 15 -- .../droid/web/StepicRestLoggedService.java | 8 +- .../domain/comment/model/CommentsData.kt | 11 ++ .../comment/repository/CommentRepository.kt | 8 + .../remote/comment/model/CommentRequest.kt | 9 + .../remote/comment/model/CommentResponse.kt | 27 +++ .../stepik/android/model/comments/Comment.kt | 154 ++++++++++-------- .../org/stepik/android/model/comments/Vote.kt | 8 +- 14 files changed, 171 insertions(+), 121 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/web/CommentRequest.kt delete mode 100644 app/src/main/java/org/stepic/droid/web/CommentsResponse.kt create mode 100644 app/src/main/java/org/stepik/android/domain/comment/model/CommentsData.kt create mode 100644 app/src/main/java/org/stepik/android/domain/comment/repository/CommentRepository.kt create mode 100644 app/src/main/java/org/stepik/android/remote/comment/model/CommentRequest.kt create mode 100644 app/src/main/java/org/stepik/android/remote/comment/model/CommentResponse.kt diff --git a/app/src/main/java/org/stepic/droid/core/CommentManager.kt b/app/src/main/java/org/stepic/droid/core/CommentManager.kt index f2e45bd623..979d5b0c44 100644 --- a/app/src/main/java/org/stepic/droid/core/CommentManager.kt +++ b/app/src/main/java/org/stepic/droid/core/CommentManager.kt @@ -8,7 +8,7 @@ import org.stepik.android.model.comments.DiscussionProxy import org.stepik.android.model.comments.Vote import org.stepic.droid.preferences.SharedPreferenceHelper import org.stepic.droid.web.Api -import org.stepic.droid.web.CommentsResponse +import org.stepik.android.remote.comment.model.CommentResponse import org.stepik.android.model.user.User import retrofit2.Call import retrofit2.Callback @@ -73,7 +73,7 @@ class CommentManager @Inject constructor( } } - private fun addComments(stepicResponse: CommentsResponse, fromReply: Boolean = false) { + private fun addComments(stepicResponse: CommentResponse, fromReply: Boolean = false) { updateOnlyCommentsIfCachedSilent(stepicResponse.comments) stepicResponse.users ?.forEach { @@ -148,9 +148,9 @@ class CommentManager @Inject constructor( } fun loadCommentsByIds(idsForLoading: LongArray, fromReply: Boolean = false) { - api.getCommentsByIds(idsForLoading).enqueue(object : Callback { + api.getCommentsByIds(idsForLoading).enqueue(object : Callback { - override fun onResponse(call: Call?, response: Response?) { + override fun onResponse(call: Call?, response: Response?) { if (response != null && response.isSuccessful) { val stepicResponse = response.body() @@ -164,7 +164,7 @@ class CommentManager @Inject constructor( } } - override fun onFailure(call: Call?, t: Throwable?) { + override fun onFailure(call: Call?, t: Throwable?) { commentsPoster.connectionProblem() } }) diff --git a/app/src/main/java/org/stepic/droid/ui/dialogs/DeleteCommentDialogFragment.kt b/app/src/main/java/org/stepic/droid/ui/dialogs/DeleteCommentDialogFragment.kt index 57d000dae4..ad1ac7d07b 100644 --- a/app/src/main/java/org/stepic/droid/ui/dialogs/DeleteCommentDialogFragment.kt +++ b/app/src/main/java/org/stepic/droid/ui/dialogs/DeleteCommentDialogFragment.kt @@ -11,7 +11,7 @@ import org.stepik.android.model.comments.Comment import org.stepic.droid.util.ProgressHelper import org.stepic.droid.util.argument import org.stepic.droid.web.Api -import org.stepic.droid.web.CommentsResponse +import org.stepik.android.remote.comment.model.CommentResponse import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -54,9 +54,9 @@ class DeleteCommentDialogFragment : DialogFragment() { .setPositiveButton(R.string.delete_label) { _, _ -> ProgressHelper.activate(loadingProgressDialog) analytic.reportEvent(Analytic.Comments.DELETE_COMMENT_CONFIRMATION) - api.deleteComment(commentId).enqueue(object : Callback { + api.deleteComment(commentId).enqueue(object : Callback { - override fun onResponse(call: Call?, response: Response?) { + override fun onResponse(call: Call?, response: Response?) { ProgressHelper.dismiss(loadingProgressDialog) if (response?.isSuccessful != true) { val comment = response?.body()?.comments?.firstOrNull() @@ -68,7 +68,7 @@ class DeleteCommentDialogFragment : DialogFragment() { } } - override fun onFailure(call: Call?, t: Throwable?) { + override fun onFailure(call: Call?, t: Throwable?) { ProgressHelper.dismiss(loadingProgressDialog) (targetFragment as DialogCallback).onDeleteConnectionProblem() } diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/NewCommentFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/NewCommentFragment.kt index 3653829084..1a09bc4513 100644 --- a/app/src/main/java/org/stepic/droid/ui/fragments/NewCommentFragment.kt +++ b/app/src/main/java/org/stepic/droid/ui/fragments/NewCommentFragment.kt @@ -20,7 +20,7 @@ import org.stepic.droid.ui.util.BackButtonHandler import org.stepic.droid.ui.util.OnBackClickListener import org.stepic.droid.ui.util.initCenteredToolbar import org.stepic.droid.util.ProgressHelper -import org.stepic.droid.web.CommentsResponse +import org.stepik.android.remote.comment.model.CommentResponse import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -148,9 +148,9 @@ class NewCommentFragment : FragmentBase(), OnBackClickListener { enableMenuItem(true) } - api.postComment(text, target!!, parent).enqueue(object : Callback { + api.postComment(text, target!!, parent).enqueue(object : Callback { - override fun onResponse(call: Call?, response: Response?) { + override fun onResponse(call: Call?, response: Response?) { if (response?.isSuccessful ?: false && response?.body()?.comments != null) { analytic.reportEvent(Analytic.Comments.COMMENTS_SENT_SUCCESSFULLY) val newComment = response.body()?.comments?.firstOrNull() @@ -169,7 +169,7 @@ class NewCommentFragment : FragmentBase(), OnBackClickListener { } } - override fun onFailure(call: Call?, t: Throwable?) { + override fun onFailure(call: Call?, t: Throwable?) { Toast.makeText(App.getAppContext(), R.string.connectionProblems, Toast.LENGTH_LONG).show() onFinishTryingSending() } diff --git a/app/src/main/java/org/stepic/droid/web/Api.java b/app/src/main/java/org/stepic/droid/web/Api.java index 4420db7ade..725f70c38c 100644 --- a/app/src/main/java/org/stepic/droid/web/Api.java +++ b/app/src/main/java/org/stepic/droid/web/Api.java @@ -18,6 +18,7 @@ import org.stepik.android.model.Tag; import org.stepik.android.model.Reply; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.comment.model.CommentResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; @@ -152,15 +153,15 @@ enum TokenType { Call getDiscussionProxies(String discussionProxyId); - Call getCommentAnd20Replies(long commentId); + Call getCommentAnd20Replies(long commentId); - Call getCommentsByIds(long[] commentIds); + Call getCommentsByIds(long[] commentIds); - Call postComment(String text, long target /*for example, related step*/, @Nullable Long parent /*put if it is reply*/); + Call postComment(String text, long target /*for example, related step*/, @Nullable Long parent /*put if it is reply*/); Call makeVote(String voteId, @Nullable Vote.Value voteValue); - Call deleteComment(long commentId); + Call deleteComment(long commentId); Call getCertificates(); diff --git a/app/src/main/java/org/stepic/droid/web/ApiImpl.java b/app/src/main/java/org/stepic/droid/web/ApiImpl.java index b072d05945..1b28979bf5 100644 --- a/app/src/main/java/org/stepic/droid/web/ApiImpl.java +++ b/app/src/main/java/org/stepic/droid/web/ApiImpl.java @@ -67,6 +67,8 @@ import org.stepik.android.model.user.Profile; import org.stepik.android.model.user.RegistrationCredentials; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.comment.model.CommentRequest; +import org.stepik.android.remote.comment.model.CommentResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; @@ -797,18 +799,18 @@ public Call getDiscussionProxies(String discussionProxy } @Override - public Call getCommentAnd20Replies(long commentId) { + public Call getCommentAnd20Replies(long commentId) { long[] id = new long[]{commentId}; return loggedService.getComments(id); } @Override - public Call getCommentsByIds(long[] commentIds) { + public Call getCommentsByIds(long[] commentIds) { return loggedService.getComments(commentIds); } @Override - public Call postComment(String text, long target, @Nullable Long parent) { + public Call postComment(String text, long target, @Nullable Long parent) { Comment comment = new Comment(target, text, parent); return loggedService.postComment(new CommentRequest(comment)); } @@ -821,7 +823,7 @@ public Call makeVote(String voteId, @Nullable Vote.Value voteValue } @Override - public Call deleteComment(long commentId) { + public Call deleteComment(long commentId) { return loggedService.deleteComment(commentId); } diff --git a/app/src/main/java/org/stepic/droid/web/CommentRequest.kt b/app/src/main/java/org/stepic/droid/web/CommentRequest.kt deleted file mode 100644 index d20acb9857..0000000000 --- a/app/src/main/java/org/stepic/droid/web/CommentRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.stepic.droid.web - -import org.stepik.android.model.comments.Comment - -class CommentRequest( - val comment: Comment -) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/CommentsResponse.kt b/app/src/main/java/org/stepic/droid/web/CommentsResponse.kt deleted file mode 100644 index 3e9794ce9a..0000000000 --- a/app/src/main/java/org/stepic/droid/web/CommentsResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.stepic.droid.web - -import org.stepik.android.model.user.User -import org.stepik.android.model.comments.Comment -import org.stepik.android.model.comments.Vote -import org.stepik.android.model.Meta - -class CommentsResponse( - val detail: String?, // "You do not have permission to perform this action.", null if OK - val target: List?, // ["Invalid pk '10205111' - object does not exist."], null if OK - val meta: Meta?, // not null, if OK - val comments: List?, - val users: List?, - val votes: List? -) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 2e729f02f0..353b99da5e 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -5,6 +5,8 @@ import org.stepic.droid.web.model.adaptive.RecommendationsResponse; import org.stepic.droid.web.model.story_templates.StoryTemplatesResponse; import org.stepik.android.remote.assignment.model.AssignmentResponse; +import org.stepik.android.remote.comment.model.CommentRequest; +import org.stepik.android.remote.comment.model.CommentResponse; import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; @@ -208,16 +210,16 @@ Single getExistingSubmissionsReactive( Call getDiscussionProxy(@Path("id") String discussionProxyId); @GET("api/comments") - Call getComments(@Query("ids[]") long[] ids); + Call getComments(@Query("ids[]") long[] ids); @POST("api/comments") - Call postComment(@Body CommentRequest comment); + Call postComment(@Body CommentRequest comment); @PUT("api/votes/{id}") Call postVote(@Path("id") String voteId, @Body VoteRequest voteRequest); @DELETE("api/comments/{id}") - Call deleteComment(@Path("id") long commentId); + Call deleteComment(@Path("id") long commentId); @GET("api/certificates") Call getCertificates(@Query("user") long userId); diff --git a/app/src/main/java/org/stepik/android/domain/comment/model/CommentsData.kt b/app/src/main/java/org/stepik/android/domain/comment/model/CommentsData.kt new file mode 100644 index 0000000000..d75ec58b30 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/comment/model/CommentsData.kt @@ -0,0 +1,11 @@ +package org.stepik.android.domain.comment.model + +import org.stepik.android.model.comments.Comment +import org.stepik.android.model.comments.Vote +import org.stepik.android.model.user.User + +data class CommentsData( + val comments: List, + val users: List, + val votes: List +) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/comment/repository/CommentRepository.kt b/app/src/main/java/org/stepik/android/domain/comment/repository/CommentRepository.kt new file mode 100644 index 0000000000..7528d0dcef --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/comment/repository/CommentRepository.kt @@ -0,0 +1,8 @@ +package org.stepik.android.domain.comment.repository + +import io.reactivex.Single +import org.stepik.android.domain.comment.model.CommentsData + +interface CommentRepository { + fun getComments(vararg commentIds: Long): Single +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/comment/model/CommentRequest.kt b/app/src/main/java/org/stepik/android/remote/comment/model/CommentRequest.kt new file mode 100644 index 0000000000..faef104636 --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/comment/model/CommentRequest.kt @@ -0,0 +1,9 @@ +package org.stepik.android.remote.comment.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.comments.Comment + +class CommentRequest( + @SerializedName("comment") + val comment: Comment +) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/comment/model/CommentResponse.kt b/app/src/main/java/org/stepik/android/remote/comment/model/CommentResponse.kt new file mode 100644 index 0000000000..8d73daaed7 --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/comment/model/CommentResponse.kt @@ -0,0 +1,27 @@ +package org.stepik.android.remote.comment.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.user.User +import org.stepik.android.model.comments.Comment +import org.stepik.android.model.comments.Vote +import org.stepik.android.model.Meta +import org.stepik.android.remote.base.model.MetaResponse + +class CommentResponse( + @SerializedName("detail") + val detail: String?, // "You do not have permission to perform this action.", null if OK + @SerializedName("target") + val target: List?, // ["Invalid pk '10205111' - object does not exist."], null if OK + + @SerializedName("meta") + override val meta: Meta, // not null, if OK + + @SerializedName("comments") + val comments: List?, + + @SerializedName("users") + val users: List?, + + @SerializedName("votes") + val votes: List? +) : MetaResponse \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/comments/Comment.kt b/model/src/main/java/org/stepik/android/model/comments/Comment.kt index 573cab643b..f26dad5350 100644 --- a/model/src/main/java/org/stepik/android/model/comments/Comment.kt +++ b/model/src/main/java/org/stepik/android/model/comments/Comment.kt @@ -13,87 +13,66 @@ import org.stepik.android.model.util.writeDate import java.util.Date data class Comment( - val id: Long = 0, - val parent: Long? = null, - val user: Long? = null, - @SerializedName("user_role") - val userRole: UserRole? = null, - val time: Date? = null, - val text: String? = "", - @SerializedName("reply_count") - val replyCount: Int? = null, + @SerializedName("id") + val id: Long = 0, + @SerializedName("parent") + val parent: Long? = null, + @SerializedName("user") + val user: Long? = null, + @SerializedName("user_role") + val userRole: UserRole? = null, + @SerializedName("time") + val time: Date? = null, + @SerializedName("text") + val text: String? = "", + @SerializedName("reply_count") + val replyCount: Int? = null, - @SerializedName("is_deleted") - val isDeleted: Boolean? = null, - @SerializedName("deleted_by") - val deletedBy: String? = null, - @SerializedName("deleted_at") - val deletedAt: String? = null, + @SerializedName("is_deleted") + val isDeleted: Boolean? = null, + @SerializedName("deleted_by") + val deletedBy: String? = null, + @SerializedName("deleted_at") + val deletedAt: String? = null, - @SerializedName("can_moderate") - val canModerate: Boolean? = null, - @SerializedName("can_delete") - val canDelete: Boolean? = null, + @SerializedName("can_moderate") + val canModerate: Boolean? = null, + @SerializedName("can_delete") + val canDelete: Boolean? = null, - val actions: Actions? = null, - val target: Long = 0, //for example, id of Step. - val replies: List? = null, //oldList of all replies, but in query only 20. + @SerializedName("actions") + val actions: Actions? = null, + @SerializedName("target") + val target: Long = 0, //for example, id of Step. + @SerializedName("replies") + val replies: List? = null, //oldList of all replies, but in query only 20. - @SerializedName("tonality_auto") - val tonalityAuto: Int? = null, - @SerializedName("tonality_manual") - val tonalityManual: Int? = null, - @SerializedName("is_pinned") - val isPinned: Boolean = false, - @SerializedName("is_staff_replied") - val isStaffReplied: Boolean? = null, - @SerializedName("is_reported") - val isReported: Boolean? = null, - @SerializedName("epic_count") - val epicCount: Int? = null, - @SerializedName("abuse_count") - val abuseCount: Int? = null, + @SerializedName("tonality_auto") + val tonalityAuto: Int? = null, + @SerializedName("tonality_manual") + val tonalityManual: Int? = null, + @SerializedName("is_pinned") + val isPinned: Boolean = false, + @SerializedName("is_staff_replied") + val isStaffReplied: Boolean? = null, + @SerializedName("is_reported") + val isReported: Boolean? = null, + @SerializedName("epic_count") + val epicCount: Int? = null, + @SerializedName("abuse_count") + val abuseCount: Int? = null, - val vote: String? = null + @SerializedName("vote") + val vote: String? = null ) : Parcelable { constructor(target: Long, text: String, parent: Long?) : this( - id = 0, - target = target, - text = text, - parent = parent + id = 0, + target = target, + text = text, + parent = parent ) - companion object CREATOR: Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): Comment = Comment( - parcel.readLong(), - parcel.readValue(Long::class.java.classLoader) as Long?, - parcel.readValue(Long::class.java.classLoader) as Long?, - UserRole.values().getOrNull(parcel.readInt()), - parcel.readDate(), - parcel.readString() ?: "", - parcel.readValue(Int::class.java.classLoader) as Int?, - parcel.readValue(Boolean::class.java.classLoader) as Boolean?, - parcel.readString(), - parcel.readString(), - parcel.readValue(Boolean::class.java.classLoader) as Boolean?, - parcel.readValue(Boolean::class.java.classLoader) as Boolean?, - parcel.readParcelable(Actions::class.java.classLoader), - parcel.readLong(), - ArrayList().apply { parcel.readList(this, Long::class.java.classLoader) }, - parcel.readValue(Int::class.java.classLoader) as Int?, - parcel.readValue(Int::class.java.classLoader) as Int?, - parcel.readBoolean(), - parcel.readValue(Boolean::class.java.classLoader) as Boolean?, - parcel.readValue(Boolean::class.java.classLoader) as Boolean?, - parcel.readValue(Int::class.java.classLoader) as Int?, - parcel.readValue(Int::class.java.classLoader) as Int?, - parcel.readString() - ) - - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - - override fun describeContents() = 0 + override fun describeContents(): Int = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeLong(id) @@ -121,4 +100,35 @@ data class Comment( dest.writeString(vote) } + companion object CREATOR: Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Comment = + Comment( + parcel.readLong(), + parcel.readValue(Long::class.java.classLoader) as Long?, + parcel.readValue(Long::class.java.classLoader) as Long?, + UserRole.values().getOrNull(parcel.readInt()), + parcel.readDate(), + parcel.readString() ?: "", + parcel.readValue(Int::class.java.classLoader) as Int?, + parcel.readValue(Boolean::class.java.classLoader) as Boolean?, + parcel.readString(), + parcel.readString(), + parcel.readValue(Boolean::class.java.classLoader) as Boolean?, + parcel.readValue(Boolean::class.java.classLoader) as Boolean?, + parcel.readParcelable(Actions::class.java.classLoader), + parcel.readLong(), + ArrayList().apply { parcel.readList(this, Long::class.java.classLoader) }, + parcel.readValue(Int::class.java.classLoader) as Int?, + parcel.readValue(Int::class.java.classLoader) as Int?, + parcel.readBoolean(), + parcel.readValue(Boolean::class.java.classLoader) as Boolean?, + parcel.readValue(Boolean::class.java.classLoader) as Boolean?, + parcel.readValue(Int::class.java.classLoader) as Int?, + parcel.readValue(Int::class.java.classLoader) as Int?, + parcel.readString() + ) + + override fun newArray(size: Int): Array = + arrayOfNulls(size) + } } \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/comments/Vote.kt b/model/src/main/java/org/stepik/android/model/comments/Vote.kt index 67f02671c9..72a8acca46 100644 --- a/model/src/main/java/org/stepik/android/model/comments/Vote.kt +++ b/model/src/main/java/org/stepik/android/model/comments/Vote.kt @@ -3,13 +3,15 @@ package org.stepik.android.model.comments import com.google.gson.annotations.SerializedName data class Vote( - val id: String, - val value: Value? + @SerializedName("id") + val id: String, + @SerializedName("value") + val value: Value? ) { enum class Value { @SerializedName("epic") LIKE, @SerializedName("abuse") - DISLIKE, + DISLIKE } } \ No newline at end of file From ca155085eee7949a713d7dccada85ea9ebb3e4c2 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 20:03:44 +0300 Subject: [PATCH 072/108] add comment repository + data source + data module --- .../droid/web/StepicRestLoggedService.java | 3 ++ .../repository/CommentRepositoryImpl.kt | 17 +++++++++++ .../comment/source/CommentRemoteDataSource.kt | 8 ++++++ .../comment/CommentRemoteDataSourceImpl.kt | 28 +++++++++++++++++++ .../section/SectionRemoteDataSourceImpl.kt | 3 +- .../injection/comment/CommentDataModule.kt | 21 ++++++++++++++ 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/data/comment/repository/CommentRepositoryImpl.kt create mode 100644 app/src/main/java/org/stepik/android/data/comment/source/CommentRemoteDataSource.kt create mode 100644 app/src/main/java/org/stepik/android/remote/comment/CommentRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/comment/CommentDataModule.kt diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 353b99da5e..807e739317 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -212,6 +212,9 @@ Single getExistingSubmissionsReactive( @GET("api/comments") Call getComments(@Query("ids[]") long[] ids); + @GET("api/comments") + Single getCommentsReactive(@Query("ids[]") long[] ids); + @POST("api/comments") Call postComment(@Body CommentRequest comment); diff --git a/app/src/main/java/org/stepik/android/data/comment/repository/CommentRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/comment/repository/CommentRepositoryImpl.kt new file mode 100644 index 0000000000..a9955a061f --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/comment/repository/CommentRepositoryImpl.kt @@ -0,0 +1,17 @@ +package org.stepik.android.data.comment.repository + +import io.reactivex.Single +import org.stepik.android.data.comment.source.CommentRemoteDataSource +import org.stepik.android.domain.comment.model.CommentsData +import org.stepik.android.domain.comment.repository.CommentRepository +import javax.inject.Inject + +class CommentRepositoryImpl +@Inject +constructor( + private val commentRemoteDataSource: CommentRemoteDataSource +) : CommentRepository { + override fun getComments(vararg commentIds: Long): Single = + commentRemoteDataSource + .getComments(*commentIds) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/data/comment/source/CommentRemoteDataSource.kt b/app/src/main/java/org/stepik/android/data/comment/source/CommentRemoteDataSource.kt new file mode 100644 index 0000000000..a2fb2160e5 --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/comment/source/CommentRemoteDataSource.kt @@ -0,0 +1,8 @@ +package org.stepik.android.data.comment.source + +import io.reactivex.Single +import org.stepik.android.domain.comment.model.CommentsData + +interface CommentRemoteDataSource { + fun getComments(vararg commentIds: Long): Single +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/comment/CommentRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/comment/CommentRemoteDataSourceImpl.kt new file mode 100644 index 0000000000..5ff66a69db --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/comment/CommentRemoteDataSourceImpl.kt @@ -0,0 +1,28 @@ +package org.stepik.android.remote.comment + +import io.reactivex.Single +import io.reactivex.functions.Function +import org.stepic.droid.web.StepicRestLoggedService +import org.stepik.android.data.comment.source.CommentRemoteDataSource +import org.stepik.android.domain.comment.model.CommentsData +import org.stepik.android.remote.comment.model.CommentResponse +import javax.inject.Inject + +class CommentRemoteDataSourceImpl +@Inject +constructor( + private val loggedService: StepicRestLoggedService +) : CommentRemoteDataSource { + private val commentResponseMapper = Function { response: CommentResponse -> + CommentsData( + comments = response.comments ?: emptyList(), + users = response.users ?: emptyList(), + votes = response.votes ?: emptyList() + ) + } + + override fun getComments(vararg commentIds: Long): Single = + loggedService + .getCommentsReactive(commentIds) + .map(commentResponseMapper) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/section/SectionRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/section/SectionRemoteDataSourceImpl.kt index d7c6e10914..a7b7412d9c 100644 --- a/app/src/main/java/org/stepik/android/remote/section/SectionRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/remote/section/SectionRemoteDataSourceImpl.kt @@ -14,8 +14,7 @@ class SectionRemoteDataSourceImpl constructor( private val api: Api ) : SectionRemoteDataSource { - private val sectionResponseMapper = - Function>(SectionResponse::sections) + private val sectionResponseMapper = Function(SectionResponse::sections) override fun getSections(vararg sectionIds: Long): Single> = sectionIds diff --git a/app/src/main/java/org/stepik/android/view/injection/comment/CommentDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/comment/CommentDataModule.kt new file mode 100644 index 0000000000..e0717c4beb --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/comment/CommentDataModule.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.injection.comment + +import dagger.Binds +import dagger.Module +import org.stepik.android.data.comment.repository.CommentRepositoryImpl +import org.stepik.android.data.comment.source.CommentRemoteDataSource +import org.stepik.android.domain.comment.repository.CommentRepository +import org.stepik.android.remote.comment.CommentRemoteDataSourceImpl + +@Module +internal abstract class CommentDataModule { + @Binds + internal abstract fun bindCommentRepository( + commentRepositoryImpl: CommentRepositoryImpl + ): CommentRepository + + @Binds + internal abstract fun bindCommentRemoteDataSource( + commentRemoteDataSourceImpl: CommentRemoteDataSourceImpl + ): CommentRemoteDataSource +} \ No newline at end of file From 736b5d8792e76f30544ade4153de294efd9b1539 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 5 Jul 2019 23:07:37 +0300 Subject: [PATCH 073/108] use comment interactor --- .../org/stepic/droid/core/CommentManager.kt | 86 ++++++++++--------- .../droid/di/comment/CommentsComponent.kt | 6 +- .../comment/interactor/CommentInteractor.kt | 16 ++++ 3 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/domain/comment/interactor/CommentInteractor.kt diff --git a/app/src/main/java/org/stepic/droid/core/CommentManager.kt b/app/src/main/java/org/stepic/droid/core/CommentManager.kt index 979d5b0c44..a07ce6f131 100644 --- a/app/src/main/java/org/stepic/droid/core/CommentManager.kt +++ b/app/src/main/java/org/stepic/droid/core/CommentManager.kt @@ -1,28 +1,37 @@ package org.stepic.droid.core +import io.reactivex.Scheduler +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.subscribeBy import org.stepic.droid.core.comments.contract.CommentsPoster import org.stepic.droid.di.comment.CommentsScope +import org.stepic.droid.di.qualifiers.BackgroundScheduler +import org.stepic.droid.di.qualifiers.MainScheduler import org.stepic.droid.model.CommentAdapterItem import org.stepik.android.model.comments.Comment import org.stepik.android.model.comments.DiscussionProxy import org.stepik.android.model.comments.Vote import org.stepic.droid.preferences.SharedPreferenceHelper -import org.stepic.droid.web.Api -import org.stepik.android.remote.comment.model.CommentResponse +import org.stepik.android.domain.comment.interactor.CommentInteractor +import org.stepik.android.domain.comment.model.CommentsData import org.stepik.android.model.user.User -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response import java.util.* import javax.inject.Inject @CommentsScope -class CommentManager @Inject constructor( - private val api: Api, - private val commentsPoster: CommentsPoster, - private val sharedPrefs: SharedPreferenceHelper +class CommentManager +@Inject +constructor( + private val commentInteractor: CommentInteractor, + private val commentsPoster: CommentsPoster, + private val sharedPrefs: SharedPreferenceHelper, + + @BackgroundScheduler + private val backgroundScheduler: Scheduler, + @MainScheduler + private val mainScheduler: Scheduler ) { - private var discussionProxy: DiscussionProxy? = null private val discussionOrderList: MutableList = ArrayList() @@ -40,6 +49,8 @@ class CommentManager @Inject constructor( private val commentIdIsLoading: MutableSet = HashSet() //can be reply or comment (with 0 replies) for load more comments). private val voteMap: MutableMap = HashMap() + private val compositeDisposable = CompositeDisposable() + fun loadComments() { val orderOfComments = discussionOrderList orderOfComments.let { @@ -73,18 +84,19 @@ class CommentManager @Inject constructor( } } - private fun addComments(stepicResponse: CommentResponse, fromReply: Boolean = false) { - updateOnlyCommentsIfCachedSilent(stepicResponse.comments) - stepicResponse.users - ?.forEach { - if (it.id !in userSetMap) { - userSetMap[it.id] = it - } + private fun addComments(commentsData: CommentsData, fromReply: Boolean = false) { + updateOnlyCommentsIfCachedSilent(commentsData.comments) + commentsData.users + .forEach { + if (it.id !in userSetMap) { + userSetMap[it.id] = it } - stepicResponse.votes?.forEach { - //updating info - voteMap[it.id] = it - } + } + commentsData.votes + .forEach { + //updating info + voteMap[it.id] = it + } //commentIdIsLoading = commentIdIsLoading.filterNot { cachedCommentsSetMap.containsKey(it) }.toHashSet() if (fromReply) { repliesIdIsLoading.clear() @@ -148,26 +160,14 @@ class CommentManager @Inject constructor( } fun loadCommentsByIds(idsForLoading: LongArray, fromReply: Boolean = false) { - api.getCommentsByIds(idsForLoading).enqueue(object : Callback { - - override fun onResponse(call: Call?, response: Response?) { - - if (response != null && response.isSuccessful) { - val stepicResponse = response.body() - if (stepicResponse != null) { - addComments(stepicResponse, fromReply) - } else { - commentsPoster.connectionProblem() - } - } else { - commentsPoster.connectionProblem() - } - } - - override fun onFailure(call: Call?, t: Throwable?) { - commentsPoster.connectionProblem() - } - }) + compositeDisposable += commentInteractor + .getComments(*idsForLoading) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { addComments(it, fromReply) }, + onError = { commentsPoster.connectionProblem() } + ) } fun getSize() = cachedCommentsList.size @@ -254,6 +254,8 @@ class CommentManager @Inject constructor( fun getVoteByVoteId(voteId: String): Vote? = voteMap[voteId] fun resetAll(dP: DiscussionProxy? = null) { + compositeDisposable.clear() + parentIdToPositionInDiscussionMap.clear() if (dP != null) { setDiscussionProxy(dP) @@ -274,6 +276,8 @@ class CommentManager @Inject constructor( fun clearAllLoadings() { commentIdIsLoading.clear() repliesIdIsLoading.clear() + + compositeDisposable.clear() } fun isDiscussionProxyNull() = (discussionProxyId == null) diff --git a/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt b/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt index cb0b31c154..95ef5c4d95 100644 --- a/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt +++ b/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt @@ -2,9 +2,13 @@ package org.stepic.droid.di.comment import dagger.Subcomponent import org.stepic.droid.ui.fragments.CommentsFragment +import org.stepik.android.view.injection.comment.CommentDataModule @CommentsScope -@Subcomponent(modules = arrayOf(CommentsModule::class)) +@Subcomponent(modules = [ + CommentsModule::class, + CommentDataModule::class +]) interface CommentsComponent { @Subcomponent.Builder diff --git a/app/src/main/java/org/stepik/android/domain/comment/interactor/CommentInteractor.kt b/app/src/main/java/org/stepik/android/domain/comment/interactor/CommentInteractor.kt new file mode 100644 index 0000000000..77695659b6 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/comment/interactor/CommentInteractor.kt @@ -0,0 +1,16 @@ +package org.stepik.android.domain.comment.interactor + +import io.reactivex.Single +import org.stepik.android.domain.comment.model.CommentsData +import org.stepik.android.domain.comment.repository.CommentRepository +import javax.inject.Inject + +class CommentInteractor +@Inject +constructor( + private val commentRepository: CommentRepository +) { + fun getComments(vararg commentIds: Long): Single = + commentRepository + .getComments(*commentIds) +} \ No newline at end of file From b69d004e3bc591cfd2073dc481a848ab7b89bbed Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Sat, 6 Jul 2019 16:12:44 +0300 Subject: [PATCH 074/108] comments -> comment_banner --- .../presenters/CommentsBannerPresenter.kt | 4 +-- .../org/stepic/droid/di/step/StepComponent.kt | 4 +-- .../droid/di/storage/StorageComponent.kt | 4 +-- .../stepic/droid/di/storage/StorageModule.kt | 6 ++-- .../storage/migration/MigrationFrom39To40.kt | 4 +-- .../CommentBannerDataCacheSourceImpl.kt | 29 +++++++++++++++++++ .../comment_banner/dao/CommentBannerDao.java | 5 ++++ .../dao/CommentBannerDaoImpl.kt} | 17 ++++++----- .../structure/DbStructureCommentBanner.kt} | 4 +-- .../CommentsBannerDataCacheSourceImpl.kt | 29 ------------------- .../cache/comments/dao/CommentsBannerDao.java | 5 ---- .../CommentBannerRepositoryImpl.kt} | 12 ++++---- .../source/CommentBannerCacheDataSource.kt} | 4 +-- .../interactor/CommentBannerInteractor.kt | 18 ++++++++++++ .../repository/CommentBannerRepository.kt} | 4 +-- .../comments/interactor/CommentsInteractor.kt | 18 ------------ .../comment_banner/CommentBannerDataModule.kt | 21 ++++++++++++++ .../comments/CommentsBannerDataModule.kt | 21 -------------- 18 files changed, 105 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/cache/comment_banner/CommentBannerDataCacheSourceImpl.kt create mode 100644 app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDao.java rename app/src/main/java/org/stepik/android/cache/{comments/dao/CommentsBannerDaoImpl.kt => comment_banner/dao/CommentBannerDaoImpl.kt} (57%) rename app/src/main/java/org/stepik/android/cache/{comments/structure/DbStructureCommentsBanner.kt => comment_banner/structure/DbStructureCommentBanner.kt} (81%) delete mode 100644 app/src/main/java/org/stepik/android/cache/comments/CommentsBannerDataCacheSourceImpl.kt delete mode 100644 app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDao.java rename app/src/main/java/org/stepik/android/data/{comments/repository/CommentsBannerRepositoryImpl.kt => comment_banner/repository/CommentBannerRepositoryImpl.kt} (57%) rename app/src/main/java/org/stepik/android/data/{comments/source/CommentsBannerCacheDataSource.kt => comment_banner/source/CommentBannerCacheDataSource.kt} (69%) create mode 100644 app/src/main/java/org/stepik/android/domain/comment_banner/interactor/CommentBannerInteractor.kt rename app/src/main/java/org/stepik/android/domain/{comments/repository/CommentsBannerRepository.kt => comment_banner/repository/CommentBannerRepository.kt} (69%) delete mode 100644 app/src/main/java/org/stepik/android/domain/comments/interactor/CommentsInteractor.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/comment_banner/CommentBannerDataModule.kt delete mode 100644 app/src/main/java/org/stepik/android/view/injection/comments/CommentsBannerDataModule.kt diff --git a/app/src/main/java/org/stepic/droid/core/presenters/CommentsBannerPresenter.kt b/app/src/main/java/org/stepic/droid/core/presenters/CommentsBannerPresenter.kt index 0c04c30af2..d50bca8516 100644 --- a/app/src/main/java/org/stepic/droid/core/presenters/CommentsBannerPresenter.kt +++ b/app/src/main/java/org/stepic/droid/core/presenters/CommentsBannerPresenter.kt @@ -9,14 +9,14 @@ import org.stepic.droid.core.presenters.contracts.CommentsView import org.stepic.droid.di.qualifiers.BackgroundScheduler import org.stepic.droid.di.qualifiers.MainScheduler import org.stepic.droid.util.emptyOnErrorStub -import org.stepik.android.domain.comments.interactor.CommentsInteractor +import org.stepik.android.domain.comment_banner.interactor.CommentBannerInteractor import timber.log.Timber import javax.inject.Inject class CommentsBannerPresenter @Inject constructor( - private val commentsBannerInteractor: CommentsInteractor, + private val commentsBannerInteractor: CommentBannerInteractor, private val commentsTooltipSplitTest: CommentsTooltipSplitTest, @BackgroundScheduler diff --git a/app/src/main/java/org/stepic/droid/di/step/StepComponent.kt b/app/src/main/java/org/stepic/droid/di/step/StepComponent.kt index 38c24ab346..ad78db0a64 100644 --- a/app/src/main/java/org/stepic/droid/di/step/StepComponent.kt +++ b/app/src/main/java/org/stepic/droid/di/step/StepComponent.kt @@ -6,11 +6,11 @@ import org.stepic.droid.di.comment.CommentsComponent import org.stepic.droid.di.step.code.CodeComponent import org.stepic.droid.di.streak.StreakModule import org.stepic.droid.ui.fragments.StepAttemptFragment -import org.stepik.android.view.injection.comments.CommentsBannerDataModule +import org.stepik.android.view.injection.comment_banner.CommentBannerDataModule import org.stepik.android.view.injection.feedback.FeedbackDataModule @StepScope -@Subcomponent(modules = arrayOf(StreakModule::class, CommentCountModule::class, CommentsBannerDataModule::class, FeedbackDataModule::class)) +@Subcomponent(modules = arrayOf(StreakModule::class, CommentCountModule::class, CommentBannerDataModule::class, FeedbackDataModule::class)) interface StepComponent { @Subcomponent.Builder interface Builder { diff --git a/app/src/main/java/org/stepic/droid/di/storage/StorageComponent.kt b/app/src/main/java/org/stepic/droid/di/storage/StorageComponent.kt index 549deeb668..ba06596413 100644 --- a/app/src/main/java/org/stepic/droid/di/storage/StorageComponent.kt +++ b/app/src/main/java/org/stepic/droid/di/storage/StorageComponent.kt @@ -9,7 +9,7 @@ import org.stepic.droid.persistence.storage.dao.PersistentItemDao import org.stepic.droid.persistence.storage.dao.PersistentStateDao import org.stepic.droid.storage.dao.IDao import org.stepic.droid.storage.operations.DatabaseFacade -import org.stepik.android.cache.comments.dao.CommentsBannerDao +import org.stepik.android.cache.comment_banner.dao.CommentBannerDao import org.stepik.android.cache.personal_deadlines.dao.PersonalDeadlinesDao import org.stepik.android.domain.course_reviews.model.CourseReview import org.stepik.android.model.CourseReviewSummary @@ -31,7 +31,7 @@ interface StorageComponent { val deadlinesDao: PersonalDeadlinesDao val deadlinesBannerDao: DeadlinesBannerDao - val commentsBannerDao: CommentsBannerDao + val commentBannerDao: CommentBannerDao val persistentItemDao: PersistentItemDao val persistentStateDao: PersistentStateDao diff --git a/app/src/main/java/org/stepic/droid/di/storage/StorageModule.kt b/app/src/main/java/org/stepic/droid/di/storage/StorageModule.kt index fa80fe96b2..03a357811b 100644 --- a/app/src/main/java/org/stepic/droid/di/storage/StorageModule.kt +++ b/app/src/main/java/org/stepic/droid/di/storage/StorageModule.kt @@ -25,8 +25,8 @@ import org.stepic.droid.storage.DatabaseHelper import org.stepic.droid.storage.dao.* import org.stepic.droid.storage.operations.* import org.stepik.android.model.ViewAssignment -import org.stepik.android.cache.comments.dao.CommentsBannerDao -import org.stepik.android.cache.comments.dao.CommentsBannerDaoImpl +import org.stepik.android.cache.comment_banner.dao.CommentBannerDao +import org.stepik.android.cache.comment_banner.dao.CommentBannerDaoImpl import org.stepik.android.cache.user.dao.UserDaoImpl import org.stepik.android.cache.video.dao.VideoEntityDaoImpl import org.stepik.android.cache.video.dao.VideoDao @@ -173,7 +173,7 @@ abstract class StorageModule { @StorageSingleton @Binds - internal abstract fun provideCommentsBannerDao(commentsBannerDaoImpl: CommentsBannerDaoImpl): CommentsBannerDao + internal abstract fun provideCommentsBannerDao(commentsBannerDaoImpl: CommentBannerDaoImpl): CommentBannerDao @StorageSingleton @Binds diff --git a/app/src/main/java/org/stepic/droid/storage/migration/MigrationFrom39To40.kt b/app/src/main/java/org/stepic/droid/storage/migration/MigrationFrom39To40.kt index 6f1fd3582b..674b54a388 100644 --- a/app/src/main/java/org/stepic/droid/storage/migration/MigrationFrom39To40.kt +++ b/app/src/main/java/org/stepic/droid/storage/migration/MigrationFrom39To40.kt @@ -1,10 +1,10 @@ package org.stepic.droid.storage.migration import android.database.sqlite.SQLiteDatabase -import org.stepik.android.cache.comments.structure.DbStructureCommentsBanner +import org.stepik.android.cache.comment_banner.structure.DbStructureCommentBanner object MigrationFrom39To40 : Migration { override fun migrate(db: SQLiteDatabase) { - DbStructureCommentsBanner.createTable(db) + DbStructureCommentBanner.createTable(db) } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/cache/comment_banner/CommentBannerDataCacheSourceImpl.kt b/app/src/main/java/org/stepik/android/cache/comment_banner/CommentBannerDataCacheSourceImpl.kt new file mode 100644 index 0000000000..368f7132f3 --- /dev/null +++ b/app/src/main/java/org/stepik/android/cache/comment_banner/CommentBannerDataCacheSourceImpl.kt @@ -0,0 +1,29 @@ +package org.stepik.android.cache.comment_banner + +import io.reactivex.Completable +import io.reactivex.Single +import org.stepik.android.cache.comment_banner.dao.CommentBannerDao +import org.stepik.android.cache.comment_banner.structure.DbStructureCommentBanner +import org.stepik.android.data.comment_banner.source.CommentBannerCacheDataSource +import javax.inject.Inject + +class CommentBannerDataCacheSourceImpl +@Inject +constructor( + private val commentBannerDao: CommentBannerDao +) : CommentBannerCacheDataSource { + override fun addCourseId(courseId: Long): Completable = + Completable.fromAction { + commentBannerDao.insertOrReplace(courseId) + } + + override fun removeCourseId(courseId: Long): Completable = + Completable.fromAction { + commentBannerDao.remove(DbStructureCommentBanner.Columns.COURSE_ID, courseId.toString()) + } + + override fun hasCourseId(courseId: Long): Single = + Single.fromCallable { + commentBannerDao.isInDb(courseId) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDao.java b/app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDao.java new file mode 100644 index 0000000000..ad5c687217 --- /dev/null +++ b/app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDao.java @@ -0,0 +1,5 @@ +package org.stepik.android.cache.comment_banner.dao; + +import org.stepic.droid.storage.dao.IDao; + +public interface CommentBannerDao extends IDao { } diff --git a/app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDaoImpl.kt b/app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDaoImpl.kt similarity index 57% rename from app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDaoImpl.kt rename to app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDaoImpl.kt index 378b6f825f..6b1385e330 100644 --- a/app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDaoImpl.kt +++ b/app/src/main/java/org/stepik/android/cache/comment_banner/dao/CommentBannerDaoImpl.kt @@ -1,31 +1,32 @@ -package org.stepik.android.cache.comments.dao +package org.stepik.android.cache.comment_banner.dao import android.content.ContentValues import android.database.Cursor import org.stepic.droid.storage.dao.DaoBase import org.stepic.droid.storage.operations.DatabaseOperations -import org.stepik.android.cache.comments.structure.DbStructureCommentsBanner +import org.stepik.android.cache.comment_banner.structure.DbStructureCommentBanner import javax.inject.Inject -class CommentsBannerDaoImpl +class CommentBannerDaoImpl @Inject constructor( databaseOperations: DatabaseOperations -) : DaoBase(databaseOperations), CommentsBannerDao { +) : DaoBase(databaseOperations), + CommentBannerDao { override fun getDbName(): String = - DbStructureCommentsBanner.COMMENTS_BANNER + DbStructureCommentBanner.COMMENTS_BANNER override fun getDefaultPrimaryColumn(): String = - DbStructureCommentsBanner.Columns.COURSE_ID + DbStructureCommentBanner.Columns.COURSE_ID override fun getDefaultPrimaryValue(persistentObject: Long?): String = persistentObject.toString() override fun getContentValues(persistentObject: Long?): ContentValues = ContentValues().apply { - put(DbStructureCommentsBanner.Columns.COURSE_ID, persistentObject) + put(DbStructureCommentBanner.Columns.COURSE_ID, persistentObject) } override fun parsePersistentObject(cursor: Cursor): Long = - cursor.getLong(cursor.getColumnIndex(DbStructureCommentsBanner.Columns.COURSE_ID)) + cursor.getLong(cursor.getColumnIndex(DbStructureCommentBanner.Columns.COURSE_ID)) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/cache/comments/structure/DbStructureCommentsBanner.kt b/app/src/main/java/org/stepik/android/cache/comment_banner/structure/DbStructureCommentBanner.kt similarity index 81% rename from app/src/main/java/org/stepik/android/cache/comments/structure/DbStructureCommentsBanner.kt rename to app/src/main/java/org/stepik/android/cache/comment_banner/structure/DbStructureCommentBanner.kt index a47c7254c7..780b1245f8 100644 --- a/app/src/main/java/org/stepik/android/cache/comments/structure/DbStructureCommentsBanner.kt +++ b/app/src/main/java/org/stepik/android/cache/comment_banner/structure/DbStructureCommentBanner.kt @@ -1,8 +1,8 @@ -package org.stepik.android.cache.comments.structure +package org.stepik.android.cache.comment_banner.structure import android.database.sqlite.SQLiteDatabase -object DbStructureCommentsBanner { +object DbStructureCommentBanner { const val COMMENTS_BANNER = "comments_banner" object Columns { diff --git a/app/src/main/java/org/stepik/android/cache/comments/CommentsBannerDataCacheSourceImpl.kt b/app/src/main/java/org/stepik/android/cache/comments/CommentsBannerDataCacheSourceImpl.kt deleted file mode 100644 index 06e72ca5a1..0000000000 --- a/app/src/main/java/org/stepik/android/cache/comments/CommentsBannerDataCacheSourceImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.stepik.android.cache.comments - -import io.reactivex.Completable -import io.reactivex.Single -import org.stepik.android.cache.comments.dao.CommentsBannerDao -import org.stepik.android.cache.comments.structure.DbStructureCommentsBanner -import org.stepik.android.data.comments.source.CommentsBannerCacheDataSource -import javax.inject.Inject - -class CommentsBannerDataCacheSourceImpl -@Inject -constructor( - private val commentsBannerDao: CommentsBannerDao -) : CommentsBannerCacheDataSource { - override fun addCourseId(courseId: Long): Completable = - Completable.fromAction { - commentsBannerDao.insertOrReplace(courseId) - } - - override fun removeCourseId(courseId: Long): Completable = - Completable.fromAction { - commentsBannerDao.remove(DbStructureCommentsBanner.Columns.COURSE_ID, courseId.toString()) - } - - override fun hasCourseId(courseId: Long): Single = - Single.fromCallable { - commentsBannerDao.isInDb(courseId) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDao.java b/app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDao.java deleted file mode 100644 index 8150e37444..0000000000 --- a/app/src/main/java/org/stepik/android/cache/comments/dao/CommentsBannerDao.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.stepik.android.cache.comments.dao; - -import org.stepic.droid.storage.dao.IDao; - -public interface CommentsBannerDao extends IDao { } diff --git a/app/src/main/java/org/stepik/android/data/comments/repository/CommentsBannerRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/comment_banner/repository/CommentBannerRepositoryImpl.kt similarity index 57% rename from app/src/main/java/org/stepik/android/data/comments/repository/CommentsBannerRepositoryImpl.kt rename to app/src/main/java/org/stepik/android/data/comment_banner/repository/CommentBannerRepositoryImpl.kt index a3128e86ae..889310e63d 100644 --- a/app/src/main/java/org/stepik/android/data/comments/repository/CommentsBannerRepositoryImpl.kt +++ b/app/src/main/java/org/stepik/android/data/comment_banner/repository/CommentBannerRepositoryImpl.kt @@ -1,16 +1,16 @@ -package org.stepik.android.data.comments.repository +package org.stepik.android.data.comment_banner.repository import io.reactivex.Completable import io.reactivex.Single -import org.stepik.android.data.comments.source.CommentsBannerCacheDataSource -import org.stepik.android.domain.comments.repository.CommentsBannerRepository +import org.stepik.android.data.comment_banner.source.CommentBannerCacheDataSource +import org.stepik.android.domain.comment_banner.repository.CommentBannerRepository import javax.inject.Inject -class CommentsBannerRepositoryImpl +class CommentBannerRepositoryImpl @Inject constructor( - private val commentsBannerCacheDataSource: CommentsBannerCacheDataSource -) : CommentsBannerRepository { + private val commentsBannerCacheDataSource: CommentBannerCacheDataSource +) : CommentBannerRepository { override fun addCourseId(courseId: Long): Completable = commentsBannerCacheDataSource.addCourseId(courseId) diff --git a/app/src/main/java/org/stepik/android/data/comments/source/CommentsBannerCacheDataSource.kt b/app/src/main/java/org/stepik/android/data/comment_banner/source/CommentBannerCacheDataSource.kt similarity index 69% rename from app/src/main/java/org/stepik/android/data/comments/source/CommentsBannerCacheDataSource.kt rename to app/src/main/java/org/stepik/android/data/comment_banner/source/CommentBannerCacheDataSource.kt index 2d21ce2f69..002f85ec85 100644 --- a/app/src/main/java/org/stepik/android/data/comments/source/CommentsBannerCacheDataSource.kt +++ b/app/src/main/java/org/stepik/android/data/comment_banner/source/CommentBannerCacheDataSource.kt @@ -1,9 +1,9 @@ -package org.stepik.android.data.comments.source +package org.stepik.android.data.comment_banner.source import io.reactivex.Completable import io.reactivex.Single -interface CommentsBannerCacheDataSource { +interface CommentBannerCacheDataSource { fun addCourseId(courseId: Long): Completable fun removeCourseId(courseId: Long): Completable fun hasCourseId(courseId: Long): Single diff --git a/app/src/main/java/org/stepik/android/domain/comment_banner/interactor/CommentBannerInteractor.kt b/app/src/main/java/org/stepik/android/domain/comment_banner/interactor/CommentBannerInteractor.kt new file mode 100644 index 0000000000..f4468e8a9e --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/comment_banner/interactor/CommentBannerInteractor.kt @@ -0,0 +1,18 @@ +package org.stepik.android.domain.comment_banner.interactor + +import io.reactivex.Completable +import io.reactivex.Single +import org.stepik.android.domain.comment_banner.repository.CommentBannerRepository +import javax.inject.Inject + +class CommentBannerInteractor +@Inject +constructor( + private val commentBannerRepository: CommentBannerRepository +) { + fun shouldShowCommentsBannerForCourse(courseId: Long): Single = + commentBannerRepository.hasCourseId(courseId) + + fun onBannerShown(courseId: Long): Completable = + commentBannerRepository.addCourseId(courseId) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/comments/repository/CommentsBannerRepository.kt b/app/src/main/java/org/stepik/android/domain/comment_banner/repository/CommentBannerRepository.kt similarity index 69% rename from app/src/main/java/org/stepik/android/domain/comments/repository/CommentsBannerRepository.kt rename to app/src/main/java/org/stepik/android/domain/comment_banner/repository/CommentBannerRepository.kt index fcd0902d39..1c6763c042 100644 --- a/app/src/main/java/org/stepik/android/domain/comments/repository/CommentsBannerRepository.kt +++ b/app/src/main/java/org/stepik/android/domain/comment_banner/repository/CommentBannerRepository.kt @@ -1,9 +1,9 @@ -package org.stepik.android.domain.comments.repository +package org.stepik.android.domain.comment_banner.repository import io.reactivex.Completable import io.reactivex.Single -interface CommentsBannerRepository { +interface CommentBannerRepository { fun addCourseId(courseId: Long): Completable fun removeCourseId(courseId: Long): Completable fun hasCourseId(courseId: Long): Single diff --git a/app/src/main/java/org/stepik/android/domain/comments/interactor/CommentsInteractor.kt b/app/src/main/java/org/stepik/android/domain/comments/interactor/CommentsInteractor.kt deleted file mode 100644 index 5bdeb3bf21..0000000000 --- a/app/src/main/java/org/stepik/android/domain/comments/interactor/CommentsInteractor.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.stepik.android.domain.comments.interactor - -import io.reactivex.Completable -import io.reactivex.Single -import org.stepik.android.domain.comments.repository.CommentsBannerRepository -import javax.inject.Inject - -class CommentsInteractor -@Inject -constructor( - private val commentsBannerRepository: CommentsBannerRepository -) { - fun shouldShowCommentsBannerForCourse(courseId: Long): Single = - commentsBannerRepository.hasCourseId(courseId) - - fun onBannerShown(courseId: Long): Completable = - commentsBannerRepository.addCourseId(courseId) -} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/comment_banner/CommentBannerDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/comment_banner/CommentBannerDataModule.kt new file mode 100644 index 0000000000..6018d92a86 --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/comment_banner/CommentBannerDataModule.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.injection.comment_banner + +import dagger.Binds +import dagger.Module +import org.stepik.android.cache.comment_banner.CommentBannerDataCacheSourceImpl +import org.stepik.android.data.comment_banner.repository.CommentBannerRepositoryImpl +import org.stepik.android.data.comment_banner.source.CommentBannerCacheDataSource +import org.stepik.android.domain.comment_banner.repository.CommentBannerRepository + +@Module +abstract class CommentBannerDataModule { + @Binds + internal abstract fun bindCommentsBannerRepository( + commentsBannerRepositoryImpl: CommentBannerRepositoryImpl + ): CommentBannerRepository + + @Binds + internal abstract fun bindCommentsBannerCacheDataSource( + commentsBannerCacheDataSourceImpl: CommentBannerDataCacheSourceImpl + ): CommentBannerCacheDataSource +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/comments/CommentsBannerDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/comments/CommentsBannerDataModule.kt deleted file mode 100644 index 4cfdb90378..0000000000 --- a/app/src/main/java/org/stepik/android/view/injection/comments/CommentsBannerDataModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.stepik.android.view.injection.comments - -import dagger.Binds -import dagger.Module -import org.stepik.android.cache.comments.CommentsBannerDataCacheSourceImpl -import org.stepik.android.data.comments.repository.CommentsBannerRepositoryImpl -import org.stepik.android.data.comments.source.CommentsBannerCacheDataSource -import org.stepik.android.domain.comments.repository.CommentsBannerRepository - -@Module -abstract class CommentsBannerDataModule { - @Binds - internal abstract fun bindCommentsBannerRepository( - commentsBannerRepositoryImpl: CommentsBannerRepositoryImpl - ): CommentsBannerRepository - - @Binds - internal abstract fun bindCommentsBannerCacheDataSource( - commentsBannerCacheDataSourceImpl: CommentsBannerDataCacheSourceImpl - ): CommentsBannerCacheDataSource -} \ No newline at end of file From 35c1ca33d681d4e9c8ca3f5e161fb78d71ce46b2 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Sat, 6 Jul 2019 17:26:01 +0300 Subject: [PATCH 075/108] migrate discussion proxy --- .../core/presenters/DiscussionPresenter.kt | 58 +++++++++---------- .../droid/di/comment/CommentsComponent.kt | 4 +- .../main/java/org/stepic/droid/web/Api.java | 3 +- .../java/org/stepic/droid/web/ApiImpl.java | 6 +- .../droid/web/DiscussionProxyResponse.kt | 11 ---- .../droid/web/StepicRestLoggedService.java | 5 +- .../DiscussionProxyRepositoryImpl.kt | 17 ++++++ .../source/DiscussionProxyRemoteDataSource.kt | 8 +++ .../interactor/DiscussionProxyInteractor.kt | 18 ++++++ .../repository/DiscussionProxyRepository.kt | 14 +++++ .../DiscussionProxyRemoteDataSourceImpl.kt | 22 +++++++ .../model/DiscussionProxyResponse.kt | 13 +++++ .../DiscussionProxyDataModule.kt | 21 +++++++ .../android/model/comments/DiscussionProxy.kt | 18 +++--- 14 files changed, 160 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/web/DiscussionProxyResponse.kt create mode 100644 app/src/main/java/org/stepik/android/data/discussion_proxy/repository/DiscussionProxyRepositoryImpl.kt create mode 100644 app/src/main/java/org/stepik/android/data/discussion_proxy/source/DiscussionProxyRemoteDataSource.kt create mode 100644 app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt create mode 100644 app/src/main/java/org/stepik/android/domain/discussion_proxy/repository/DiscussionProxyRepository.kt create mode 100644 app/src/main/java/org/stepik/android/remote/discussion_proxy/DiscussionProxyRemoteDataSourceImpl.kt create mode 100644 app/src/main/java/org/stepik/android/remote/discussion_proxy/model/DiscussionProxyResponse.kt create mode 100644 app/src/main/java/org/stepik/android/view/injection/discussion_proxy/DiscussionProxyDataModule.kt diff --git a/app/src/main/java/org/stepic/droid/core/presenters/DiscussionPresenter.kt b/app/src/main/java/org/stepic/droid/core/presenters/DiscussionPresenter.kt index 6f8714c82d..6feea5e310 100644 --- a/app/src/main/java/org/stepic/droid/core/presenters/DiscussionPresenter.kt +++ b/app/src/main/java/org/stepic/droid/core/presenters/DiscussionPresenter.kt @@ -1,44 +1,44 @@ package org.stepic.droid.core.presenters -import org.stepic.droid.concurrency.MainHandler +import io.reactivex.Scheduler +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import io.reactivex.rxkotlin.subscribeBy import org.stepic.droid.core.presenters.contracts.DiscussionView import org.stepic.droid.di.comment.CommentsScope -import org.stepic.droid.web.Api -import java.util.concurrent.ThreadPoolExecutor +import org.stepic.droid.di.qualifiers.BackgroundScheduler +import org.stepic.droid.di.qualifiers.MainScheduler +import org.stepik.android.domain.discussion_proxy.interactor.DiscussionProxyInteractor import javax.inject.Inject @CommentsScope class DiscussionPresenter -@Inject constructor( - private val threadPoolExecutor: ThreadPoolExecutor, - private val mainHandler: MainHandler, - private val api: Api) : PresenterBase() { +@Inject +constructor( + private val discussionProxyInteractor: DiscussionProxyInteractor, + + @BackgroundScheduler + private val backgroundScheduler: Scheduler, + @MainScheduler + private val mainScheduler: Scheduler +) : PresenterBase() { + private val compositeDisposable = CompositeDisposable() fun loadDiscussion(discussionId: String) { - threadPoolExecutor.execute { - try { - val discussionProxy = api - .getDiscussionProxies(discussionId) - .execute() - .body() - ?.discussionProxies!! - .first() - if (discussionProxy.discussions.isEmpty()) { - mainHandler.post { + compositeDisposable.clear() + compositeDisposable += discussionProxyInteractor + .getDiscussionProxy(discussionId) + .subscribeOn(backgroundScheduler) + .observeOn(mainScheduler) + .subscribeBy( + onSuccess = { discussionProxy -> + if (discussionProxy.discussions.isEmpty()) { view?.onEmptyComments(discussionProxy) - } - } else { - mainHandler.post { + } else { view?.onLoaded(discussionProxy) } - } - } catch (exception: Exception) { - mainHandler.post { - view?.onInternetProblemInComments() - } - } - } - + }, + onError = { view?.onInternetProblemInComments() } + ) } - } diff --git a/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt b/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt index 95ef5c4d95..4f65264ef1 100644 --- a/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt +++ b/app/src/main/java/org/stepic/droid/di/comment/CommentsComponent.kt @@ -3,11 +3,13 @@ package org.stepic.droid.di.comment import dagger.Subcomponent import org.stepic.droid.ui.fragments.CommentsFragment import org.stepik.android.view.injection.comment.CommentDataModule +import org.stepik.android.view.injection.discussion_proxy.DiscussionProxyDataModule @CommentsScope @Subcomponent(modules = [ CommentsModule::class, - CommentDataModule::class + CommentDataModule::class, + DiscussionProxyDataModule::class ]) interface CommentsComponent { diff --git a/app/src/main/java/org/stepic/droid/web/Api.java b/app/src/main/java/org/stepic/droid/web/Api.java index 725f70c38c..63b2ff9ad9 100644 --- a/app/src/main/java/org/stepic/droid/web/Api.java +++ b/app/src/main/java/org/stepic/droid/web/Api.java @@ -22,6 +22,7 @@ import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; +import org.stepik.android.remote.discussion_proxy.model.DiscussionProxyResponse; import org.stepik.android.remote.email_address.model.EmailAddressResponse; import org.stepik.android.remote.last_step.model.LastStepResponse; import org.stepik.android.remote.lesson.model.LessonResponse; @@ -151,8 +152,6 @@ enum TokenType { Call removeDevice(long deviceId); - Call getDiscussionProxies(String discussionProxyId); - Call getCommentAnd20Replies(long commentId); Call getCommentsByIds(long[] commentIds); diff --git a/app/src/main/java/org/stepic/droid/web/ApiImpl.java b/app/src/main/java/org/stepic/droid/web/ApiImpl.java index 1b28979bf5..2f626ccc04 100644 --- a/app/src/main/java/org/stepic/droid/web/ApiImpl.java +++ b/app/src/main/java/org/stepic/droid/web/ApiImpl.java @@ -72,6 +72,7 @@ import org.stepik.android.remote.course.model.CourseResponse; import org.stepik.android.remote.course.model.CourseReviewSummaryResponse; import org.stepik.android.remote.course.model.EnrollmentRequest; +import org.stepik.android.remote.discussion_proxy.model.DiscussionProxyResponse; import org.stepik.android.remote.email_address.model.EmailAddressResponse; import org.stepik.android.remote.last_step.model.LastStepResponse; import org.stepik.android.remote.lesson.model.LessonResponse; @@ -793,11 +794,6 @@ public Call removeDevice(long deviceId) { return loggedService.removeDevice(deviceId); } - @Override - public Call getDiscussionProxies(String discussionProxyId) { - return loggedService.getDiscussionProxy(discussionProxyId); - } - @Override public Call getCommentAnd20Replies(long commentId) { long[] id = new long[]{commentId}; diff --git a/app/src/main/java/org/stepic/droid/web/DiscussionProxyResponse.kt b/app/src/main/java/org/stepic/droid/web/DiscussionProxyResponse.kt deleted file mode 100644 index f4a054fa39..0000000000 --- a/app/src/main/java/org/stepic/droid/web/DiscussionProxyResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.stepic.droid.web - -import com.google.gson.annotations.SerializedName -import org.stepik.android.model.comments.DiscussionProxy -import org.stepik.android.model.Meta - - class DiscussionProxyResponse( - val meta: Meta?, - @SerializedName("discussion-proxies") - val discussionProxies: List -) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 807e739317..a908e454c7 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -14,6 +14,7 @@ import org.stepik.android.remote.course_payments.model.CoursePaymentsResponse; import org.stepik.android.remote.course_reviews.model.CourseReviewRequest; import org.stepik.android.remote.course_reviews.model.CourseReviewsResponse; +import org.stepik.android.remote.discussion_proxy.model.DiscussionProxyResponse; import org.stepik.android.remote.email_address.model.EmailAddressResponse; import org.stepik.android.remote.last_step.model.LastStepResponse; import org.stepik.android.remote.lesson.model.LessonResponse; @@ -206,8 +207,8 @@ Single getExistingSubmissionsReactive( @DELETE("api/devices/{id}") Call removeDevice(@Path("id") long deviceId); - @GET("api/discussion-proxies/{id}") - Call getDiscussionProxy(@Path("id") String discussionProxyId); + @GET("api/discussion-proxies") + Single getDiscussionProxies(@Query("ids[]") String[] ids); @GET("api/comments") Call getComments(@Query("ids[]") long[] ids); diff --git a/app/src/main/java/org/stepik/android/data/discussion_proxy/repository/DiscussionProxyRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/discussion_proxy/repository/DiscussionProxyRepositoryImpl.kt new file mode 100644 index 0000000000..fdb7f0ffdc --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/discussion_proxy/repository/DiscussionProxyRepositoryImpl.kt @@ -0,0 +1,17 @@ +package org.stepik.android.data.discussion_proxy.repository + +import io.reactivex.Single +import org.stepik.android.data.discussion_proxy.source.DiscussionProxyRemoteDataSource +import org.stepik.android.domain.discussion_proxy.repository.DiscussionProxyRepository +import org.stepik.android.model.comments.DiscussionProxy +import javax.inject.Inject + +class DiscussionProxyRepositoryImpl +@Inject +constructor( + private val discussionProxyRemoteDataSource: DiscussionProxyRemoteDataSource +) : DiscussionProxyRepository { + override fun getDiscussionProxies(vararg discussionProxyIds: String): Single> = + discussionProxyRemoteDataSource + .getDiscussionProxies(*discussionProxyIds) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/data/discussion_proxy/source/DiscussionProxyRemoteDataSource.kt b/app/src/main/java/org/stepik/android/data/discussion_proxy/source/DiscussionProxyRemoteDataSource.kt new file mode 100644 index 0000000000..58de27512e --- /dev/null +++ b/app/src/main/java/org/stepik/android/data/discussion_proxy/source/DiscussionProxyRemoteDataSource.kt @@ -0,0 +1,8 @@ +package org.stepik.android.data.discussion_proxy.source + +import io.reactivex.Single +import org.stepik.android.model.comments.DiscussionProxy + +interface DiscussionProxyRemoteDataSource { + fun getDiscussionProxies(vararg discussionProxyIds: String): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt b/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt new file mode 100644 index 0000000000..cc14e051b8 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt @@ -0,0 +1,18 @@ +package org.stepik.android.domain.discussion_proxy.interactor + +import io.reactivex.Maybe +import io.reactivex.Single +import org.stepik.android.domain.discussion_proxy.repository.DiscussionProxyRepository +import org.stepik.android.model.comments.DiscussionProxy +import javax.inject.Inject + +class DiscussionProxyInteractor +@Inject +constructor( + private val discussionProxyRepository: DiscussionProxyRepository +) { + fun getDiscussionProxy(discussionProxyId: String): Single = + discussionProxyRepository + .getDiscussionProxy(discussionProxyId) + .toSingle() +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/domain/discussion_proxy/repository/DiscussionProxyRepository.kt b/app/src/main/java/org/stepik/android/domain/discussion_proxy/repository/DiscussionProxyRepository.kt new file mode 100644 index 0000000000..292e721938 --- /dev/null +++ b/app/src/main/java/org/stepik/android/domain/discussion_proxy/repository/DiscussionProxyRepository.kt @@ -0,0 +1,14 @@ +package org.stepik.android.domain.discussion_proxy.repository + +import io.reactivex.Maybe +import io.reactivex.Single +import org.stepic.droid.util.maybeFirst +import org.stepik.android.model.comments.DiscussionProxy + +interface DiscussionProxyRepository { + fun getDiscussionProxy(discussionProxyId: String): Maybe = + getDiscussionProxies(discussionProxyId) + .maybeFirst() + + fun getDiscussionProxies(vararg discussionProxyIds: String): Single> +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/discussion_proxy/DiscussionProxyRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/discussion_proxy/DiscussionProxyRemoteDataSourceImpl.kt new file mode 100644 index 0000000000..ec5dceca1c --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/discussion_proxy/DiscussionProxyRemoteDataSourceImpl.kt @@ -0,0 +1,22 @@ +package org.stepik.android.remote.discussion_proxy + +import io.reactivex.Single +import io.reactivex.functions.Function +import org.stepic.droid.web.StepicRestLoggedService +import org.stepik.android.data.discussion_proxy.source.DiscussionProxyRemoteDataSource +import org.stepik.android.model.comments.DiscussionProxy +import org.stepik.android.remote.discussion_proxy.model.DiscussionProxyResponse +import javax.inject.Inject + +class DiscussionProxyRemoteDataSourceImpl +@Inject +constructor( + private val loggedService: StepicRestLoggedService +) : DiscussionProxyRemoteDataSource { + private val discussionProxyResponseMapper = Function(DiscussionProxyResponse::discussionProxies) + + override fun getDiscussionProxies(vararg discussionProxyIds: String): Single> = + loggedService + .getDiscussionProxies(discussionProxyIds) + .map(discussionProxyResponseMapper) +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/discussion_proxy/model/DiscussionProxyResponse.kt b/app/src/main/java/org/stepik/android/remote/discussion_proxy/model/DiscussionProxyResponse.kt new file mode 100644 index 0000000000..b0773c740b --- /dev/null +++ b/app/src/main/java/org/stepik/android/remote/discussion_proxy/model/DiscussionProxyResponse.kt @@ -0,0 +1,13 @@ +package org.stepik.android.remote.discussion_proxy.model + +import com.google.gson.annotations.SerializedName +import org.stepik.android.model.comments.DiscussionProxy +import org.stepik.android.model.Meta +import org.stepik.android.remote.base.model.MetaResponse + +class DiscussionProxyResponse( + @SerializedName("meta") + override val meta: Meta, + @SerializedName("discussion-proxies") + val discussionProxies: List +) : MetaResponse \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/injection/discussion_proxy/DiscussionProxyDataModule.kt b/app/src/main/java/org/stepik/android/view/injection/discussion_proxy/DiscussionProxyDataModule.kt new file mode 100644 index 0000000000..31c05130af --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/injection/discussion_proxy/DiscussionProxyDataModule.kt @@ -0,0 +1,21 @@ +package org.stepik.android.view.injection.discussion_proxy + +import dagger.Binds +import dagger.Module +import org.stepik.android.data.discussion_proxy.repository.DiscussionProxyRepositoryImpl +import org.stepik.android.data.discussion_proxy.source.DiscussionProxyRemoteDataSource +import org.stepik.android.domain.discussion_proxy.repository.DiscussionProxyRepository +import org.stepik.android.remote.discussion_proxy.DiscussionProxyRemoteDataSourceImpl + +@Module +internal abstract class DiscussionProxyDataModule { + @Binds + internal abstract fun bindDiscussionProxyRepository( + discussionProxyRepositoryImpl: DiscussionProxyRepositoryImpl + ): DiscussionProxyRepository + + @Binds + internal abstract fun bindDiscussionProxyRemoteDataSource( + discussionProxyRemoteDataSourceImpl: DiscussionProxyRemoteDataSourceImpl + ): DiscussionProxyRemoteDataSource +} \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/comments/DiscussionProxy.kt b/model/src/main/java/org/stepik/android/model/comments/DiscussionProxy.kt index caf9570043..5934368c81 100644 --- a/model/src/main/java/org/stepik/android/model/comments/DiscussionProxy.kt +++ b/model/src/main/java/org/stepik/android/model/comments/DiscussionProxy.kt @@ -3,13 +3,15 @@ package org.stepik.android.model.comments import com.google.gson.annotations.SerializedName data class DiscussionProxy( - val id: String, - val discussions: List, + @SerializedName("id") + val id: String, + @SerializedName("discussions") + val discussions: List, - @SerializedName("discussions_most_liked") - val discussionsMostLiked: List, - @SerializedName("discussions_most_active") - val discussionsMostActive: List, - @SerializedName("discussions_recent_activity") - val discussionsRecentActivity: List + @SerializedName("discussions_most_liked") + val discussionsMostLiked: List, + @SerializedName("discussions_most_active") + val discussionsMostActive: List, + @SerializedName("discussions_recent_activity") + val discussionsRecentActivity: List ) \ No newline at end of file From e439964ca22ffcf1fcc42a460ff9ef2e474e2ba6 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Sat, 6 Jul 2019 17:28:15 +0300 Subject: [PATCH 076/108] clean up --- .../discussion_proxy/interactor/DiscussionProxyInteractor.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt b/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt index cc14e051b8..a916322e16 100644 --- a/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt +++ b/app/src/main/java/org/stepik/android/domain/discussion_proxy/interactor/DiscussionProxyInteractor.kt @@ -1,6 +1,5 @@ package org.stepik.android.domain.discussion_proxy.interactor -import io.reactivex.Maybe import io.reactivex.Single import org.stepik.android.domain.discussion_proxy.repository.DiscussionProxyRepository import org.stepik.android.model.comments.DiscussionProxy From 94ff3466ac92749db52438b6ffbf3090d8241724 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 8 Jul 2019 11:38:52 +0300 Subject: [PATCH 077/108] wrap feedback in pre tags --- .../mapper/StepQuizFeedbackMapper.kt | 22 ++++++++++++++++--- .../step_quiz/ui/delegate/StepQuizDelegate.kt | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt index 7aab6d867b..dc1888e19e 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/mapper/StepQuizFeedbackMapper.kt @@ -1,18 +1,20 @@ package org.stepik.android.view.step_quiz.mapper +import org.stepic.droid.util.AppConstants +import org.stepik.android.model.Step import org.stepik.android.model.Submission import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.model.StepQuizFeedbackState class StepQuizFeedbackMapper { - fun mapToStepQuizFeedbackState(state: StepQuizView.State): StepQuizFeedbackState = + fun mapToStepQuizFeedbackState(step: Step, state: StepQuizView.State): StepQuizFeedbackState = if (state is StepQuizView.State.AttemptLoaded && state.submissionState is StepQuizView.SubmissionState.Loaded) { when (state.submissionState.submission.status) { Submission.Status.CORRECT -> - StepQuizFeedbackState.Correct(state.submissionState.submission.hint?.takeIf(String::isNotEmpty)) + StepQuizFeedbackState.Correct(formatHint(step, state.submissionState.submission)) Submission.Status.WRONG -> - StepQuizFeedbackState.Wrong(state.submissionState.submission.hint?.takeIf(String::isNotEmpty)) + StepQuizFeedbackState.Wrong(formatHint(step, state.submissionState.submission)) Submission.Status.EVALUATION -> StepQuizFeedbackState.Evaluation @@ -23,4 +25,18 @@ class StepQuizFeedbackMapper { } else { StepQuizFeedbackState.Idle } + + private fun formatHint(step: Step, submission: Submission): String? = + submission + .hint + ?.takeIf(String::isNotEmpty) + ?.replace("\n", "
") + ?.let { + val showLaTeX = step.block?.name == AppConstants.TYPE_MATH // LaTeX support only for math feedback https://github.com/bioinf/edy/blob/dca133bc752ee0b21a2a419c1b7c6d5be3859a3e/apps/frontend/stepic/app/templates/components/submission-show.hbs#L48 + if (showLaTeX) { + it + } else { + """
$it
""" + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt index f6af78ed1c..415fedbfb6 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizDelegate.kt @@ -52,7 +52,7 @@ class StepQuizDelegate( fun setState(state: StepQuizView.State.AttemptLoaded) { currentState = state - stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(state)) + stepQuizFeedbackBlocksDelegate.setState(stepQuizFeedbackMapper.mapToStepQuizFeedbackState(step, state)) stepQuizFormDelegate.setState(state) stepQuizActionButton.isEnabled = StepQuizFormResolver.isQuizActionEnabled(state) From 24c0f9f2e9334acd6a2a48d16e6ac7471657080b Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 8 Jul 2019 13:56:40 +0300 Subject: [PATCH 078/108] fix typo --- .../data/submission/repository/SubmissionRepositoryImpl.kt | 2 +- .../data/submission/source/SubmissionRemoteDataSource.kt | 2 +- .../android/remote/submission/SubmissionRemoteDataSourceImpl.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt b/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt index b4947dd99a..339823290a 100644 --- a/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt +++ b/app/src/main/java/org/stepik/android/data/submission/repository/SubmissionRepositoryImpl.kt @@ -15,7 +15,7 @@ constructor( submissionRemoteDataSource.createSubmission(submission) override fun getSubmissionsForAttempt(attemptId: Long): Single> = - submissionRemoteDataSource.getSubmissionsForAttemtp(attemptId) + submissionRemoteDataSource.getSubmissionsForAttempt(attemptId) override fun getSubmissionsForStep(stepId: Long): Single> = submissionRemoteDataSource.getSubmissionsForStep(stepId) diff --git a/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt b/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt index 68b7021dd9..57deccb17d 100644 --- a/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt +++ b/app/src/main/java/org/stepik/android/data/submission/source/SubmissionRemoteDataSource.kt @@ -5,6 +5,6 @@ import org.stepik.android.model.Submission interface SubmissionRemoteDataSource { fun createSubmission(submission: Submission): Single - fun getSubmissionsForAttemtp(attemptId: Long): Single> + fun getSubmissionsForAttempt(attemptId: Long): Single> fun getSubmissionsForStep(stepId: Long): Single> } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt b/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt index c2cd5929b8..112c501c87 100644 --- a/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt +++ b/app/src/main/java/org/stepik/android/remote/submission/SubmissionRemoteDataSourceImpl.kt @@ -21,7 +21,7 @@ constructor( .map(submissionMapper) .first() - override fun getSubmissionsForAttemtp(attemptId: Long): Single> = + override fun getSubmissionsForAttempt(attemptId: Long): Single> = api.getSubmissionsReactive(attemptId) .map(submissionMapper) From 562e1f7436a76302b326ac33e3ceb18e682f3195 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Mon, 8 Jul 2019 14:29:04 +0300 Subject: [PATCH 079/108] custom quiz item view to handle item click and web view scroll inside item --- .../ui/custom/LatexSupportableWebView.java | 1 + .../stepic/droid/ui/custom/QuizItemView.kt | 49 +++++++++++++++++++ .../ui/adapter/ChoicesAdapterDelegate.kt | 2 +- app/src/main/res/layout/item_choice_quiz.xml | 4 +- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt diff --git a/app/src/main/java/org/stepic/droid/ui/custom/LatexSupportableWebView.java b/app/src/main/java/org/stepic/droid/ui/custom/LatexSupportableWebView.java index 7d47eb1dd4..5d3337fff2 100644 --- a/app/src/main/java/org/stepic/droid/ui/custom/LatexSupportableWebView.java +++ b/app/src/main/java/org/stepic/droid/ui/custom/LatexSupportableWebView.java @@ -93,6 +93,7 @@ public boolean onLongClick(View v) { webSettings.setMediaPlaybackRequiresUserGesture(false); } addJavascriptInterface(new OnScrollWebListener(), HtmlHelper.HORIZONTAL_SCROLL_LISTENER); + setSoundEffectsEnabled(false); } public void setTextIsSelectable(boolean isSelectable) { diff --git a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt new file mode 100644 index 0000000000..4c404c5ab0 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt @@ -0,0 +1,49 @@ +package org.stepic.droid.ui.custom + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.ViewConfiguration +import android.webkit.WebView +import android.widget.FrameLayout +import kotlinx.android.synthetic.main.latex_supportabe_enhanced_view.view.* +import timber.log.Timber +import java.util.* + +/** + * Custom item view to infer clicks on the item and scrolling of the webview inside the item + */ +class QuizItemView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + companion object { + const val MAX_CLICK_DURATION = 200 + } + + private var startClickTime: Long = 0 + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + val dispatched = super.dispatchTouchEvent(ev) + if (ev?.action == MotionEvent.ACTION_DOWN) { + startClickTime = Calendar.getInstance().timeInMillis + } + if (ev?.action == MotionEvent.ACTION_UP) { + onTouchEvent(ev) + } + super.onTouchEvent(ev) + return dispatched + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + return false + } + + override fun performClick(): Boolean { + val clickDuration = Calendar.getInstance().timeInMillis - startClickTime + if (clickDuration < MAX_CLICK_DURATION) { + return super.performClick() + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 56c897f486..213ccda63c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -32,7 +32,7 @@ class ChoicesAdapterDelegate( private val layerListDrawableDelegate: LayerListDrawableDelegate init { - root.setOnClickListener { onClick(itemData as Choice) } + root.itemChoiceContainer.setOnClickListener { onClick(itemData as Choice) } layerListDrawableDelegate = LayerListDrawableDelegate( listOf( R.id.not_checked_layer, diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index ba3b066bfa..7bb48c93dc 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -7,7 +7,7 @@ android:layout_marginBottom="8dp" android:orientation="vertical"> - - + Date: Mon, 8 Jul 2019 15:28:57 +0300 Subject: [PATCH 080/108] fix clicking outside of webview --- app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt index 4c404c5ab0..cba2f2690f 100644 --- a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt +++ b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt @@ -24,7 +24,8 @@ class QuizItemView @JvmOverloads constructor( private var startClickTime: Long = 0 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - val dispatched = super.dispatchTouchEvent(ev) + val webview = getChildAt(2) as ProgressLatexView + val dispatched = webview.webView.dispatchTouchEvent(ev) if (ev?.action == MotionEvent.ACTION_DOWN) { startClickTime = Calendar.getInstance().timeInMillis } From 66de27913a7494cf9acae43aa1f54faa90eac825 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 01:29:47 +0300 Subject: [PATCH 081/108] choice quiz related strings added --- app/src/main/res/values-ru/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index afeb460025..d693990009 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -727,8 +727,10 @@ Введите математическую формулу Напишите текст (строку) Напишите эссе + Выберите один или несколько элементов Ответ не может быть пустым + Выберите вариант Выберите почтовый клиент diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bea33fe4b5..d0b05fcbb5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -757,8 +757,10 @@ Enter a math formula Write an answer in form of a text (string) Write an essay + Choose one or multiple options An answer cannot be empty + Choose an option Choose email app From 70b48528a462b7acb00c8a4502531e1515994382 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 01:32:07 +0300 Subject: [PATCH 082/108] modify quiz view --- .../res/layout/fragment_step_quiz_choice.xml | 8 ------- .../res/layout/layout_step_quiz_choice.xml | 24 +++++++++++++++++++ .../res/layout/view_choice_quiz_attempt.xml | 13 ---------- 3 files changed, 24 insertions(+), 21 deletions(-) delete mode 100644 app/src/main/res/layout/fragment_step_quiz_choice.xml create mode 100644 app/src/main/res/layout/layout_step_quiz_choice.xml delete mode 100644 app/src/main/res/layout/view_choice_quiz_attempt.xml diff --git a/app/src/main/res/layout/fragment_step_quiz_choice.xml b/app/src/main/res/layout/fragment_step_quiz_choice.xml deleted file mode 100644 index 3d29a6f33c..0000000000 --- a/app/src/main/res/layout/fragment_step_quiz_choice.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_step_quiz_choice.xml b/app/src/main/res/layout/layout_step_quiz_choice.xml new file mode 100644 index 0000000000..94d8140e65 --- /dev/null +++ b/app/src/main/res/layout/layout_step_quiz_choice.xml @@ -0,0 +1,24 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_choice_quiz_attempt.xml b/app/src/main/res/layout/view_choice_quiz_attempt.xml deleted file mode 100644 index 2738a61cb3..0000000000 --- a/app/src/main/res/layout/view_choice_quiz_attempt.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file From ee3278eab33d66bde1d8c51d26d3bfc4756470e5 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 01:34:00 +0300 Subject: [PATCH 083/108] updated choice model --- .../presentation/step_quiz_choice/model/Choice.kt | 3 ++- .../ui/adapter/ChoicesAdapterDelegate.kt | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt index f16bf20a63..60d26df024 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt @@ -3,5 +3,6 @@ package org.stepik.android.presentation.step_quiz_choice.model data class Choice( val option: String, var correct: Boolean? = null, - var tip: String? = null + var feedback: String? = null, + var isEnabled: Boolean = false ) \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 213ccda63c..2430819fbe 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -32,7 +32,11 @@ class ChoicesAdapterDelegate( private val layerListDrawableDelegate: LayerListDrawableDelegate init { - root.itemChoiceContainer.setOnClickListener { onClick(itemData as Choice) } + root.itemChoiceContainer.setOnClickListener { + if (it.isEnabled) { + onClick(itemData as Choice) + } + } layerListDrawableDelegate = LayerListDrawableDelegate( listOf( R.id.not_checked_layer, @@ -45,6 +49,7 @@ class ChoicesAdapterDelegate( } override fun onBind(data: Choice) { + itemView.itemChoiceContainer.isEnabled = data.isEnabled itemView.isSelected = selectionHelper.isSelected(adapterPosition) itemChoiceCheckmark.visibility = View.INVISIBLE itemChoiceLatex.setAnyText(data.option) @@ -53,13 +58,13 @@ class ChoicesAdapterDelegate( } private fun bindTip(data: Choice) { - if (data.tip == null) { + if (data.feedback == null) { itemChoiceFeedback.visibility = View.GONE } else { itemChoiceFeedback.apply { visibility = View.VISIBLE - text = data.tip + text = data.feedback } } } @@ -72,7 +77,7 @@ class ChoicesAdapterDelegate( R.id.correct_layer } false -> { - if (data.tip == null) { + if (data.feedback == null) { R.id.incorrect_layer } else { R.id.incorrect_layer_with_tip From fe11d558e91c12746fb553ad1d6d10b772ae48f1 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 01:35:11 +0300 Subject: [PATCH 084/108] finish choice quiz fragment and form delegate setup --- .../view/injection/step/StepComponent.kt | 3 + .../ui/factory/StepQuizFragmentFactoryImpl.kt | 2 +- .../ui/delegate/ChoiceQuizFormDelegate.kt | 78 ++++++----- .../ui/fragment/ChoiceStepQuizFragment.kt | 126 ++++++++++++++---- 4 files changed, 146 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt index adcee577f8..931a69d818 100644 --- a/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt +++ b/app/src/main/java/org/stepik/android/view/injection/step/StepComponent.kt @@ -7,6 +7,7 @@ import org.stepik.android.view.injection.step_quiz.StepQuizModule import org.stepik.android.view.injection.step_quiz.StepQuizPresentationModule import org.stepik.android.view.injection.submission.SubmissionDataModule import org.stepik.android.view.step.ui.fragment.StepFragment +import org.stepik.android.view.step_quiz_choice.ui.fragment.ChoiceStepQuizFragment import org.stepik.android.view.step_quiz_text.ui.fragment.TextStepQuizFragment @Subcomponent(modules = [ @@ -27,4 +28,6 @@ interface StepComponent { fun inject(stepFragment: StepFragment) fun inject(textStepQuizFragment: TextStepQuizFragment) + + fun inject(choiceStepQuizFragment: ChoiceStepQuizFragment) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index cd075c8c11..330e437268 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -19,7 +19,7 @@ constructor() : StepQuizFragmentFactory { AppConstants.TYPE_FREE_ANSWER -> TextStepQuizFragment.newInstance(stepPersistentWrapper, lessonData) AppConstants.TYPE_CHOICE -> - ChoiceStepQuizFragment.newInstance(stepPersistentWrapper) + ChoiceStepQuizFragment.newInstance(stepPersistentWrapper, lessonData) else -> Fragment() diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index cc5043706a..ed1d46483b 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -2,13 +2,15 @@ package org.stepik.android.view.step_quiz_choice.ui.delegate import android.support.v7.widget.LinearLayoutManager import android.view.View -import kotlinx.android.synthetic.main.view_choice_quiz_attempt.view.* +import kotlinx.android.synthetic.main.fragment_step_quiz.view.* +import kotlinx.android.synthetic.main.layout_step_quiz_choice.view.* +import org.stepic.droid.R import org.stepik.android.model.Reply import org.stepik.android.model.Submission -import org.stepik.android.model.attempts.Attempt import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.presentation.step_quiz_choice.model.Choice +import org.stepik.android.view.step_quiz.resolver.StepQuizFormResolver import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate import ru.nobird.android.ui.adapterssupport.DefaultDelegateAdapter @@ -17,71 +19,75 @@ import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelper class ChoiceQuizFormDelegate( - private val choiceAttemptView: View + containerView: View ) : StepQuizFormDelegate { + private val context = containerView.context + private val quizDescription = containerView.stepQuizDescription private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() - private lateinit var selectionHelper: SelectionHelper + private var selectionHelper: SelectionHelper? = null init { - choiceAttemptView.choices_recycler.apply { + containerView.choices_recycler.apply { adapter = choicesAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) } + quizDescription.setText(R.string.step_quiz_choice_description) } - var isEnabled: Boolean = true + override fun setState(state: StepQuizView.State.AttemptLoaded) { + val dataset = state.attempt.dataset ?: return + + val submission = (state.submissionState as? StepQuizView.SubmissionState.Loaded) + ?.submission + + val reply = submission?.reply + + choicesAdapter.items = dataset.options?.map { Choice(it, isEnabled = StepQuizFormResolver.isQuizEnabled(state)) } ?: return - fun setAttempt(attempt: Attempt) { - val dataSet = attempt.dataset - dataSet?.options?.let { options -> - choicesAdapter.items = options.map { Choice(it) } - selectionHelper = if (dataSet.isMultipleChoice) { + if (selectionHelper == null) { + selectionHelper = if (dataset.isMultipleChoice) { MultipleChoiceSelectionHelper(choicesAdapter) } else { SingleChoiceSelectionHelper(choicesAdapter) } - choicesAdapter += ChoicesAdapterDelegate(selectionHelper, onClick = ::handleChoiceClick) } - } - - fun setSubmission(submission: Submission) { - submission.reply?.choices?.let { setChoices(it)} - } - - override fun setState(state: StepQuizView.State.AttemptLoaded) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + if (choicesAdapter.delegates.isEmpty()) { + choicesAdapter += ChoicesAdapterDelegate(selectionHelper as SelectionHelper, onClick = ::handleChoiceClick) + } + selectionHelper?.reset() + setChoices(reply?.choices, submission?.status) } override fun createReply(): ReplyResult { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - fun validateForm(): String? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + val choices = (0 until choicesAdapter.itemCount).map { selectionHelper?.isSelected(it) as Boolean } + return if (choices.contains(true)) { + ReplyResult.Success(Reply(choices = choices)) + } else { + ReplyResult.Error(context.getString(R.string.step_quiz_choice_empty_reply)) + } } private fun handleChoiceClick(choice: Choice) { - if (!isEnabled) return when (selectionHelper) { is SingleChoiceSelectionHelper -> { - selectionHelper.reset() - selectionHelper.select(choicesAdapter.items.indexOf(choice)) - choicesAdapter.notifyDataSetChanged() + selectionHelper?.select(choicesAdapter.items.indexOf(choice)) } is MultipleChoiceSelectionHelper -> { - selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) + selectionHelper?.toggle(choicesAdapter.items.indexOf(choice)) } } } - private fun setChoices(choices: List) { - (0 until choices.size).forEach {pos -> + private fun setChoices(choices: List?, status: Submission.Status?) { + if (choices == null) return + (0 until choices.size).forEach { pos -> if (choices[pos]) { - selectionHelper.select(pos) - choicesAdapter.items[pos].apply { - correct = true - // tip = "This is a tip\n new line" + selectionHelper?.select(pos) + choicesAdapter.items[pos].correct = when (status) { + Submission.Status.CORRECT -> true + Submission.Status.WRONG -> false + else -> null } } } diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index d39e9a504a..dd37cee0b5 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -1,51 +1,125 @@ package org.stepik.android.view.step_quiz_choice.ui.fragment +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders import android.os.Bundle +import android.support.design.widget.Snackbar import android.support.v4.app.Fragment +import android.support.v4.content.ContextCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import kotlinx.android.synthetic.main.fragment_step_quiz_choice.* +import kotlinx.android.synthetic.main.error_no_connection_with_button_small.view.* +import kotlinx.android.synthetic.main.fragment_step_quiz.* +import kotlinx.android.synthetic.main.layout_step_quiz_choice.* +import kotlinx.android.synthetic.main.view_step_quiz_submit_button.* import org.stepic.droid.R +import org.stepic.droid.base.App +import org.stepic.droid.fonts.FontsProvider import org.stepic.droid.persistence.model.StepPersistentWrapper import org.stepic.droid.util.argument -import org.stepik.android.model.Reply -import org.stepik.android.model.Submission -import org.stepik.android.model.attempts.Attempt -import org.stepik.android.model.attempts.Dataset -import org.stepik.android.model.attempts.DatasetWrapper +import org.stepic.droid.util.setTextColor +import org.stepik.android.domain.lesson.model.LessonData +import org.stepik.android.presentation.step_quiz.StepQuizPresenter +import org.stepik.android.presentation.step_quiz.StepQuizView +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate +import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate import org.stepik.android.view.step_quiz_choice.ui.delegate.ChoiceQuizFormDelegate +import org.stepik.android.view.ui.delegate.ViewStateDelegate +import javax.inject.Inject -class ChoiceStepQuizFragment: Fragment() { +class ChoiceStepQuizFragment: Fragment(), StepQuizView { companion object { - fun newInstance(stepPersistentWrapper: StepPersistentWrapper): Fragment = - ChoiceStepQuizFragment() - .apply { - this.stepWrapper = stepPersistentWrapper - } + fun newInstance(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Fragment = + ChoiceStepQuizFragment() + .apply { + this.stepWrapper = stepPersistentWrapper + this.lessonData = lessonData + } } + @Inject + internal lateinit var viewModelFactory: ViewModelProvider.Factory + + @Inject + internal lateinit var fontsProvider: FontsProvider + + private lateinit var presenter: StepQuizPresenter + + private var lessonData: LessonData by argument() private var stepWrapper: StepPersistentWrapper by argument() - private lateinit var choiceQuizFormDelegate: ChoiceQuizFormDelegate + + private lateinit var viewStateDelegate: ViewStateDelegate + private lateinit var stepQuizDelegate: StepQuizDelegate + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + injectComponent() + + presenter = ViewModelProviders.of(this, viewModelFactory).get(StepQuizPresenter::class.java) + presenter.onStepData(stepWrapper, lessonData) + } + + private fun injectComponent() { + App.component() + .stepComponentBuilder() + .build() + .inject(this) + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = - inflater.inflate(R.layout.fragment_step_quiz_choice, container, false) + (inflater.inflate(R.layout.fragment_step_quiz, container, false) as ViewGroup) + .apply { + addView(inflater.inflate(R.layout.layout_step_quiz_choice, this, false)) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - choiceQuizFormDelegate = ChoiceQuizFormDelegate(choice_quiz_attempt) - choiceQuizFormDelegate.setAttempt(Attempt( - _dataset = DatasetWrapper( - Dataset( - options = listOf("Variant 1", "\$P_{1000, 0.002}(7) \\approx P_{\\lambda}(7) = \\frac{\\lambda^7}{7!} \\cdot e^{-\\lambda} = \\frac{2^7 \\cdot e^{-2}}{7!} \\approx 0.003437 = P_{1000, 0.002}(7) \\approx P_{\\lambda}(7)\$", "Variant 3\nExtra lineExtra lineExtra lineExtra lineExtra lineExtra lineExtra lineExtra line", "

"), - isMultipleChoice = true - ) + + viewStateDelegate = ViewStateDelegate() + viewStateDelegate.addState() + viewStateDelegate.addState(stepQuizProgress) + viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, choices_recycler, stepQuizDescription, stepQuizAction) + viewStateDelegate.addState(stepQuizNetworkError) + + stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } + + stepQuizDelegate = + StepQuizDelegate( + step = stepWrapper.step, + stepQuizFormDelegate = ChoiceQuizFormDelegate(view), + stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks, fontsProvider), + stepQuizActionButton = stepQuizAction, + stepQuizDiscountingPolicy = stepQuizDiscountingPolicy, + stepQuizPresenter = presenter ) - )) - choiceQuizFormDelegate.isEnabled = true -// choiceQuizFormDelegate.setSubmission(Submission( -// reply = Reply(choices = listOf(true, true, false, true)) -// )) + } + + override fun onStart() { + super.onStart() + presenter.attachView(this) + } + + override fun onStop() { + presenter.detachView(this) + stepQuizDelegate.syncReplyState() + super.onStop() + } + + override fun setState(state: StepQuizView.State) { + viewStateDelegate.switchState(state) + if (state is StepQuizView.State.AttemptLoaded) { + stepQuizDelegate.setState(state) + } + } + + override fun showNetworkError() { + val view = view ?: return + Snackbar + .make(view, R.string.no_connection, Snackbar.LENGTH_SHORT) + .setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + .show() } } \ No newline at end of file From 4d8ae40d57333ba722ee1cbc027eb19b0fdf6208 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 16:12:58 +0300 Subject: [PATCH 085/108] added feedback field to submission model and created feedback deserializer --- .../deserializers/FeedbackDeserializer.kt | 24 +++++++++++++++++++ .../java/org/stepic/droid/web/ApiImpl.java | 3 +++ .../org/stepik/android/model/Submission.kt | 13 ++++++++-- .../android/model/feedback/ChoiceFeedback.kt | 8 +++++++ .../stepik/android/model/feedback/Feedback.kt | 3 +++ .../android/model/feedback/StringFeedback.kt | 5 ++++ 6 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/stepic/droid/jsonHelpers/deserializers/FeedbackDeserializer.kt create mode 100644 model/src/main/java/org/stepik/android/model/feedback/ChoiceFeedback.kt create mode 100644 model/src/main/java/org/stepik/android/model/feedback/Feedback.kt create mode 100644 model/src/main/java/org/stepik/android/model/feedback/StringFeedback.kt diff --git a/app/src/main/java/org/stepic/droid/jsonHelpers/deserializers/FeedbackDeserializer.kt b/app/src/main/java/org/stepic/droid/jsonHelpers/deserializers/FeedbackDeserializer.kt new file mode 100644 index 0000000000..b385a015a3 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/jsonHelpers/deserializers/FeedbackDeserializer.kt @@ -0,0 +1,24 @@ +package org.stepic.droid.jsonHelpers.deserializers + +import com.google.gson.* +import org.stepik.android.model.feedback.ChoiceFeedback +import org.stepik.android.model.feedback.Feedback +import org.stepik.android.model.feedback.StringFeedback +import java.lang.Exception +import java.lang.reflect.Type + +class FeedbackDeserializer : JsonDeserializer { + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Feedback { + return if (json !is JsonObject) { + try { + val field = context.deserialize(json, String::class.java) + StringFeedback(field) + } catch (e: Exception) { + StringFeedback() + } + } else { + context.deserialize(json, ChoiceFeedback::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/web/ApiImpl.java b/app/src/main/java/org/stepic/droid/web/ApiImpl.java index d998362374..a468a7d621 100644 --- a/app/src/main/java/org/stepic/droid/web/ApiImpl.java +++ b/app/src/main/java/org/stepic/droid/web/ApiImpl.java @@ -32,6 +32,7 @@ import org.stepic.droid.jsonHelpers.adapters.CodeOptionsAdapterFactory; import org.stepic.droid.jsonHelpers.adapters.UTCDateAdapter; import org.stepic.droid.jsonHelpers.deserializers.DatasetDeserializer; +import org.stepic.droid.jsonHelpers.deserializers.FeedbackDeserializer; import org.stepic.droid.jsonHelpers.deserializers.ReplyDeserializer; import org.stepic.droid.jsonHelpers.serializers.ReplySerializer; import org.stepic.droid.model.NotificationCategory; @@ -64,6 +65,7 @@ import org.stepik.android.model.attempts.DatasetWrapper; import org.stepik.android.model.comments.Comment; import org.stepik.android.model.comments.Vote; +import org.stepik.android.model.feedback.Feedback; import org.stepik.android.model.user.Profile; import org.stepik.android.model.user.RegistrationCredentials; import org.stepik.android.remote.assignment.model.AssignmentResponse; @@ -378,6 +380,7 @@ private Converter.Factory generateGsonFactory() { .registerTypeAdapter(ReplyWrapper.class, new ReplyDeserializer()) .registerTypeAdapter(ReplyWrapper.class, new ReplySerializer()) .registerTypeAdapter(Date.class, new UTCDateAdapter()) + .registerTypeAdapter(Feedback.class, new FeedbackDeserializer()) .create(); return GsonConverterFactory.create(gson); } diff --git a/model/src/main/java/org/stepik/android/model/Submission.kt b/model/src/main/java/org/stepik/android/model/Submission.kt index 7e7f9220b0..f7697a78a7 100644 --- a/model/src/main/java/org/stepik/android/model/Submission.kt +++ b/model/src/main/java/org/stepik/android/model/Submission.kt @@ -1,6 +1,7 @@ package org.stepik.android.model import com.google.gson.annotations.SerializedName +import org.stepik.android.model.feedback.Feedback class Submission( @SerializedName("id") @@ -19,7 +20,9 @@ class Submission( @SerializedName("session") val session: String? = null, @SerializedName("eta") - val eta: String? = null + val eta: String? = null, + @SerializedName("feedback") + val feedback: Feedback? = null ) { @Deprecated("this compatibility constructor will be removed after rewriting StepAttemptFragment in Kotlin") constructor(reply: Reply?, attempt: Long, status: Status?): this(id = 0, reply = reply, attempt = attempt, status = status) @@ -30,7 +33,13 @@ class Submission( val reply: Reply? // this virtual property allows to work with reply like it regular class field without additional wrapper get() = replyWrapper?.reply - enum class Status(val scope: String) { +// @SerializedName("feedback") +// private val feedBackWrapper: FeedBackWrapper? = feedback?.let(::FeedBackWrapper) +// +// val feedback: Feedback? +// get() = feedBackWrapper?.feedback + + enum class Status(val scope: String) { @SerializedName("correct") CORRECT("correct"), diff --git a/model/src/main/java/org/stepik/android/model/feedback/ChoiceFeedback.kt b/model/src/main/java/org/stepik/android/model/feedback/ChoiceFeedback.kt new file mode 100644 index 0000000000..d59c423be6 --- /dev/null +++ b/model/src/main/java/org/stepik/android/model/feedback/ChoiceFeedback.kt @@ -0,0 +1,8 @@ +package org.stepik.android.model.feedback + +import com.google.gson.annotations.SerializedName + +data class ChoiceFeedback( + @SerializedName("options_feedback") + val optionsFeedback: List? = null +): Feedback \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/feedback/Feedback.kt b/model/src/main/java/org/stepik/android/model/feedback/Feedback.kt new file mode 100644 index 0000000000..59be83f8ee --- /dev/null +++ b/model/src/main/java/org/stepik/android/model/feedback/Feedback.kt @@ -0,0 +1,3 @@ +package org.stepik.android.model.feedback + +interface Feedback \ No newline at end of file diff --git a/model/src/main/java/org/stepik/android/model/feedback/StringFeedback.kt b/model/src/main/java/org/stepik/android/model/feedback/StringFeedback.kt new file mode 100644 index 0000000000..5928854564 --- /dev/null +++ b/model/src/main/java/org/stepik/android/model/feedback/StringFeedback.kt @@ -0,0 +1,5 @@ +package org.stepik.android.model.feedback + +data class StringFeedback( + val stringFeedback: String? = null +): Feedback \ No newline at end of file From d775cadd06d4fbc0545e5e807231fa399825f5e7 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 16:17:05 +0300 Subject: [PATCH 086/108] disable item animator for recyclerview --- .../view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index ed1d46483b..3f01bc7633 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -29,6 +29,7 @@ class ChoiceQuizFormDelegate( init { containerView.choices_recycler.apply { + itemAnimator = null adapter = choicesAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) } From 9a4ccf164ca5fda1dbfc32c0c4bb87e2fa728675 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 20:01:13 +0300 Subject: [PATCH 087/108] added LaTeX support to quiz item hint --- .../ui/adapter/ChoicesAdapterDelegate.kt | 16 +++++----- .../ui/delegate/ChoiceQuizFormDelegate.kt | 22 +++++++++----- .../ui/fragment/ChoiceStepQuizFragment.kt | 2 +- app/src/main/res/layout/item_choice_quiz.xml | 30 +++++++++++-------- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 2430819fbe..ec723b3f29 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -5,6 +5,8 @@ import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.item_choice_quiz.view.* import org.stepic.droid.R +import org.stepic.droid.fonts.FontType +import org.stepic.droid.fonts.FontsProvider import org.stepik.android.presentation.step_quiz_choice.model.Choice import org.stepik.android.view.step_quiz_choice.ui.delegate.LayerListDrawableDelegate import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate @@ -12,6 +14,7 @@ import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper class ChoicesAdapterDelegate( + private val fontsProvider: FontsProvider, private val selectionHelper: SelectionHelper, private val onClick: (Choice) -> Unit ): AdapterDelegate>() { @@ -28,6 +31,7 @@ class ChoicesAdapterDelegate( private val itemChoiceContainer = root.itemChoiceContainer private val itemChoiceCheckmark = root.itemChoiceCheckmark private val itemChoiceLatex = root.itemChoiceLatex + private val itemChoiceFeedbackContainer = root.itemChoiceFeedbackContainer private val itemChoiceFeedback = root.itemChoiceFeedback private val layerListDrawableDelegate: LayerListDrawableDelegate @@ -58,14 +62,12 @@ class ChoicesAdapterDelegate( } private fun bindTip(data: Choice) { - if (data.feedback == null) { - itemChoiceFeedback.visibility = View.GONE + if (data.feedback.isNullOrEmpty()) { + itemChoiceFeedbackContainer.visibility = View.GONE } else { - itemChoiceFeedback.apply { - visibility = View.VISIBLE - text = data.feedback - } + itemChoiceFeedbackContainer.visibility = View.VISIBLE + itemChoiceFeedback.setPlainOrLaTeXTextWithCustomFontColored(data.feedback, fontsProvider.provideFontPath(FontType.mono), R.color.new_accent_color, true) } } @@ -77,7 +79,7 @@ class ChoicesAdapterDelegate( R.id.correct_layer } false -> { - if (data.feedback == null) { + if (data.feedback.isNullOrEmpty()) { R.id.incorrect_layer } else { R.id.incorrect_layer_with_tip diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 3f01bc7633..dfa6162f20 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -5,8 +5,10 @@ import android.view.View import kotlinx.android.synthetic.main.fragment_step_quiz.view.* import kotlinx.android.synthetic.main.layout_step_quiz_choice.view.* import org.stepic.droid.R +import org.stepic.droid.fonts.FontsProvider import org.stepik.android.model.Reply import org.stepik.android.model.Submission +import org.stepik.android.model.feedback.ChoiceFeedback import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult import org.stepik.android.presentation.step_quiz_choice.model.Choice @@ -19,7 +21,8 @@ import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelper class ChoiceQuizFormDelegate( - containerView: View + containerView: View, + private val fontsProvider: FontsProvider ) : StepQuizFormDelegate { private val context = containerView.context @@ -54,10 +57,10 @@ class ChoiceQuizFormDelegate( } } if (choicesAdapter.delegates.isEmpty()) { - choicesAdapter += ChoicesAdapterDelegate(selectionHelper as SelectionHelper, onClick = ::handleChoiceClick) + choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper as SelectionHelper, onClick = ::handleChoiceClick) } selectionHelper?.reset() - setChoices(reply?.choices, submission?.status) + setChoices(reply?.choices, submission?.status, submission?.feedback as? ChoiceFeedback) } override fun createReply(): ReplyResult { @@ -80,15 +83,18 @@ class ChoiceQuizFormDelegate( } } - private fun setChoices(choices: List?, status: Submission.Status?) { + private fun setChoices(choices: List?, status: Submission.Status?, choiceFeedback: ChoiceFeedback?) { if (choices == null) return (0 until choices.size).forEach { pos -> if (choices[pos]) { selectionHelper?.select(pos) - choicesAdapter.items[pos].correct = when (status) { - Submission.Status.CORRECT -> true - Submission.Status.WRONG -> false - else -> null + choicesAdapter.items[pos].apply { + correct = when (status) { + Submission.Status.CORRECT -> true + Submission.Status.WRONG -> false + else -> null + } + feedback = choiceFeedback?.optionsFeedback?.get(pos) ?: "" } } } diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index dd37cee0b5..9ffc8d033f 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -88,7 +88,7 @@ class ChoiceStepQuizFragment: Fragment(), StepQuizView { stepQuizDelegate = StepQuizDelegate( step = stepWrapper.step, - stepQuizFormDelegate = ChoiceQuizFormDelegate(view), + stepQuizFormDelegate = ChoiceQuizFormDelegate(view, fontsProvider), stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks, fontsProvider), stepQuizActionButton = stepQuizAction, stepQuizDiscountingPolicy = stepQuizDiscountingPolicy, diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index 7bb48c93dc..700050a57f 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -1,6 +1,5 @@ - + android:visibility="gone"> + + + From fc76b6605c2a5d56b90cd24c6dcd56c55d415b1d Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 21:48:07 +0300 Subject: [PATCH 088/108] added pre lollipop drawable resources --- .../main/res/drawable/bg_choice_checked_ripple.xml | 11 +++++++++++ .../res/drawable/bg_choice_not_checked_ripple.xml | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 app/src/main/res/drawable/bg_choice_checked_ripple.xml create mode 100644 app/src/main/res/drawable/bg_choice_not_checked_ripple.xml diff --git a/app/src/main/res/drawable/bg_choice_checked_ripple.xml b/app/src/main/res/drawable/bg_choice_checked_ripple.xml new file mode 100644 index 0000000000..a3ed373d3f --- /dev/null +++ b/app/src/main/res/drawable/bg_choice_checked_ripple.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_not_checked_ripple.xml b/app/src/main/res/drawable/bg_choice_not_checked_ripple.xml new file mode 100644 index 0000000000..b0e37261b7 --- /dev/null +++ b/app/src/main/res/drawable/bg_choice_not_checked_ripple.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file From 08016aad1ac5f56c0e1eace352e118ac1b10a944 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 21:49:33 +0300 Subject: [PATCH 089/108] not checked choice hint background --- app/src/main/res/drawable/bg_choice_quiz_item.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/drawable/bg_choice_quiz_item.xml b/app/src/main/res/drawable/bg_choice_quiz_item.xml index 029d12cf7a..ec066df18c 100644 --- a/app/src/main/res/drawable/bg_choice_quiz_item.xml +++ b/app/src/main/res/drawable/bg_choice_quiz_item.xml @@ -14,6 +14,15 @@ + + + + + + + @@ -46,7 +55,7 @@ - + Date: Tue, 9 Jul 2019 21:50:19 +0300 Subject: [PATCH 090/108] clean up --- .../ui/factory/StepQuizFragmentFactoryImpl.kt | 1 - .../ui/adapter/ChoicesAdapterDelegate.kt | 22 +++++++++++++------ .../ui/delegate/ChoiceQuizFormDelegate.kt | 2 +- .../ui/fragment/ChoiceStepQuizFragment.kt | 3 +-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt index 330e437268..adbd848230 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/factory/StepQuizFragmentFactoryImpl.kt @@ -23,7 +23,6 @@ constructor() : StepQuizFragmentFactory { else -> Fragment() - } override fun isStepCanHaveQuiz(stepPersistentWrapper: StepPersistentWrapper): Boolean = diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index ec723b3f29..1cbdbda45f 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -17,7 +17,7 @@ class ChoicesAdapterDelegate( private val fontsProvider: FontsProvider, private val selectionHelper: SelectionHelper, private val onClick: (Choice) -> Unit -): AdapterDelegate>() { +) : AdapterDelegate>() { override fun isForViewType(position: Int, data: Choice): Boolean = true @@ -44,10 +44,11 @@ class ChoicesAdapterDelegate( layerListDrawableDelegate = LayerListDrawableDelegate( listOf( R.id.not_checked_layer, + R.id.not_checked_layer_with_hint, R.id.checked_layer, R.id.correct_layer, R.id.incorrect_layer, - R.id.incorrect_layer_with_tip + R.id.incorrect_layer_with_hint ), itemChoiceContainer.background.mutate() as LayerDrawable) } @@ -57,17 +58,20 @@ class ChoicesAdapterDelegate( itemView.isSelected = selectionHelper.isSelected(adapterPosition) itemChoiceCheckmark.visibility = View.INVISIBLE itemChoiceLatex.setAnyText(data.option) - layerListDrawableDelegate.showLayer(inferChoiceId(data)) + layerListDrawableDelegate.showLayer(inferChoiceId(data)) bindTip(data) } private fun bindTip(data: Choice) { if (data.feedback.isNullOrEmpty()) { itemChoiceFeedbackContainer.visibility = View.GONE - } else { itemChoiceFeedbackContainer.visibility = View.VISIBLE - itemChoiceFeedback.setPlainOrLaTeXTextWithCustomFontColored(data.feedback, fontsProvider.provideFontPath(FontType.mono), R.color.new_accent_color, true) + itemChoiceFeedback.setPlainOrLaTeXTextWithCustomFontColored( + data.feedback, fontsProvider.provideFontPath(FontType.mono), + R.color.new_accent_color, + true + ) } } @@ -82,7 +86,7 @@ class ChoicesAdapterDelegate( if (data.feedback.isNullOrEmpty()) { R.id.incorrect_layer } else { - R.id.incorrect_layer_with_tip + R.id.incorrect_layer_with_hint } } else -> { @@ -90,7 +94,11 @@ class ChoicesAdapterDelegate( } } } else { - R.id.not_checked_layer + if (data.feedback.isNullOrEmpty()) { + R.id.not_checked_layer + } else { + R.id.not_checked_layer_with_hint + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index dfa6162f20..388fd76cdd 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -94,9 +94,9 @@ class ChoiceQuizFormDelegate( Submission.Status.WRONG -> false else -> null } - feedback = choiceFeedback?.optionsFeedback?.get(pos) ?: "" } } + choicesAdapter.items[pos].feedback = choiceFeedback?.optionsFeedback?.get(pos) ?: "" } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index 9ffc8d033f..688c2e3786 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -28,7 +28,7 @@ import org.stepik.android.view.step_quiz_choice.ui.delegate.ChoiceQuizFormDelega import org.stepik.android.view.ui.delegate.ViewStateDelegate import javax.inject.Inject -class ChoiceStepQuizFragment: Fragment(), StepQuizView { +class ChoiceStepQuizFragment : Fragment(), StepQuizView { companion object { fun newInstance(stepPersistentWrapper: StepPersistentWrapper, lessonData: LessonData): Fragment = ChoiceStepQuizFragment() @@ -73,7 +73,6 @@ class ChoiceStepQuizFragment: Fragment(), StepQuizView { addView(inflater.inflate(R.layout.layout_step_quiz_choice, this, false)) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) From f6a3f54010dabe6fb08226504600741ec0ebdea1 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Tue, 9 Jul 2019 23:22:51 +0300 Subject: [PATCH 091/108] linter fixes --- .../view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 1cbdbda45f..5abed581fe 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -58,7 +58,7 @@ class ChoicesAdapterDelegate( itemView.isSelected = selectionHelper.isSelected(adapterPosition) itemChoiceCheckmark.visibility = View.INVISIBLE itemChoiceLatex.setAnyText(data.option) - layerListDrawableDelegate.showLayer(inferChoiceId(data)) + layerListDrawableDelegate.showLayer(inferChoiceId(data)) bindTip(data) } From 1b74d692b716da162db199dd0b342c73392dffa1 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Wed, 10 Jul 2019 12:38:19 +0300 Subject: [PATCH 092/108] remove redundant view --- app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt | 2 +- app/src/main/res/layout/item_choice_quiz.xml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt index cba2f2690f..7f29023e1a 100644 --- a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt +++ b/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt @@ -24,7 +24,7 @@ class QuizItemView @JvmOverloads constructor( private var startClickTime: Long = 0 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - val webview = getChildAt(2) as ProgressLatexView + val webview = getChildAt(1) as ProgressLatexView val dispatched = webview.webView.dispatchTouchEvent(ev) if (ev?.action == MotionEvent.ACTION_DOWN) { startClickTime = Calendar.getInstance().timeInMillis diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_choice_quiz.xml index 700050a57f..256b1d246d 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_choice_quiz.xml @@ -20,10 +20,6 @@ android:layout_gravity="center_vertical|end" app:srcCompat="@drawable/ic_correct_checkmark"/> - - Date: Wed, 10 Jul 2019 19:08:15 +0300 Subject: [PATCH 093/108] pr review related fixes --- .../droid/util/GradientDrawableExtensions.kt | 13 ---- .../step_quiz_choice/model/ChoiceColor.kt | 16 ----- .../ui/adapter/ChoicesAdapterDelegate.kt | 19 +++--- .../ui/delegate/ChoiceQuizFormDelegate.kt | 18 +++-- .../ui/fragment/ChoiceStepQuizFragment.kt | 2 +- .../step_quiz_choice/ui/view}/QuizItemView.kt | 24 +++---- ...=> bg_step_quiz_choice_checked_ripple.xml} | 10 +-- ...g_step_quiz_choice_not_checked_ripple.xml} | 8 +-- .../main/res/drawable/bg_choice_quiz_item.xml | 67 ------------------- .../bg_compound_step_quiz_feedback_tip.xml | 2 +- ...=> bg_step_quiz_choice_checked_ripple.xml} | 2 +- .../res/drawable/bg_step_quiz_choice_item.xml | 67 +++++++++++++++++++ ...g_step_quiz_choice_not_checked_ripple.xml} | 6 +- ...ice_quiz.xml => item_step_quiz_choice.xml} | 8 +-- .../res/layout/layout_step_quiz_choice.xml | 2 +- app/src/main/res/values/colors.xml | 18 ++--- app/src/main/res/values/dimens.xml | 1 + .../org/stepik/android/model/Submission.kt | 6 -- 18 files changed, 127 insertions(+), 162 deletions(-) delete mode 100644 app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt delete mode 100644 app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt rename app/src/main/java/org/{stepic/droid/ui/custom => stepik/android/view/step_quiz_choice/ui/view}/QuizItemView.kt (65%) rename app/src/main/res/drawable-v21/{bg_choice_checked_ripple.xml => bg_step_quiz_choice_checked_ripple.xml} (53%) rename app/src/main/res/drawable-v21/{bg_choice_not_checked_ripple.xml => bg_step_quiz_choice_not_checked_ripple.xml} (59%) delete mode 100644 app/src/main/res/drawable/bg_choice_quiz_item.xml rename app/src/main/res/drawable/{bg_choice_checked_ripple.xml => bg_step_quiz_choice_checked_ripple.xml} (81%) create mode 100644 app/src/main/res/drawable/bg_step_quiz_choice_item.xml rename app/src/main/res/drawable/{bg_choice_not_checked_ripple.xml => bg_step_quiz_choice_not_checked_ripple.xml} (50%) rename app/src/main/res/layout/{item_choice_quiz.xml => item_step_quiz_choice.xml} (88%) diff --git a/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt b/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt deleted file mode 100644 index d47b94eaa5..0000000000 --- a/app/src/main/java/org/stepic/droid/util/GradientDrawableExtensions.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.stepic.droid.util - -import android.graphics.drawable.GradientDrawable - -fun GradientDrawable.setTopRoundedCorners(radius: Float) { - val radiiArray = FloatArray(8) - (0 until 4).forEach { radiiArray[it] = radius } - this.cornerRadii = radiiArray -} - -fun GradientDrawable.setRoundedCorners(radius: Float) { - this.cornerRadii = FloatArray(8) { radius } -} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt deleted file mode 100644 index 749fabf69b..0000000000 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/ChoiceColor.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.stepik.android.presentation.step_quiz_choice.model - -import android.support.annotation.ColorRes -import org.stepic.droid.R - -enum class ChoiceColor( - @ColorRes - val backgroundColor: Int, - @ColorRes - val strokeColor: Int -) { - CHECKED(R.color.choice_checked, R.color.choice_checked_border), - NOT_CHECKED(R.color.choice_not_checked, R.color.choice_not_checked_border), - CORRECT(R.color.choice_correct, R.color.choice_correct_border), - INCORRECT(R.color.choice_incorrect, R.color.choice_incorrect_border) -} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index 5abed581fe..fc9d89f8d6 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -3,7 +3,7 @@ package org.stepik.android.view.step_quiz_choice.ui.adapter import android.graphics.drawable.LayerDrawable import android.view.View import android.view.ViewGroup -import kotlinx.android.synthetic.main.item_choice_quiz.view.* +import kotlinx.android.synthetic.main.item_step_quiz_choice.view.* import org.stepic.droid.R import org.stepic.droid.fonts.FontType import org.stepic.droid.fonts.FontsProvider @@ -22,7 +22,7 @@ class ChoicesAdapterDelegate( true override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = - ViewHolder(createView(parent, R.layout.item_choice_quiz)) + ViewHolder(createView(parent, R.layout.item_step_quiz_choice)) inner class ViewHolder( root: View @@ -56,13 +56,17 @@ class ChoicesAdapterDelegate( override fun onBind(data: Choice) { itemView.itemChoiceContainer.isEnabled = data.isEnabled itemView.isSelected = selectionHelper.isSelected(adapterPosition) - itemChoiceCheckmark.visibility = View.INVISIBLE + if (data.correct == true) { + itemChoiceCheckmark.visibility = View.VISIBLE + } else { + itemChoiceCheckmark.visibility = View.INVISIBLE + } itemChoiceLatex.setAnyText(data.option) - layerListDrawableDelegate.showLayer(inferChoiceId(data)) - bindTip(data) + layerListDrawableDelegate.showLayer(getItemBackgroundLayer(data)) + bindHint(data) } - private fun bindTip(data: Choice) { + private fun bindHint(data: Choice) { if (data.feedback.isNullOrEmpty()) { itemChoiceFeedbackContainer.visibility = View.GONE } else { @@ -75,11 +79,10 @@ class ChoicesAdapterDelegate( } } - private fun inferChoiceId(data: Choice): Int = + private fun getItemBackgroundLayer(data: Choice): Int = if (itemView.isSelected) { when (data.correct) { true -> { - itemChoiceCheckmark.visibility = View.VISIBLE R.id.correct_layer } false -> { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 388fd76cdd..48ea7d8482 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -28,10 +28,10 @@ class ChoiceQuizFormDelegate( private val quizDescription = containerView.stepQuizDescription private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() - private var selectionHelper: SelectionHelper? = null + private lateinit var selectionHelper: SelectionHelper init { - containerView.choices_recycler.apply { + containerView.choicesRecycler.apply { itemAnimator = null adapter = choicesAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) @@ -49,17 +49,15 @@ class ChoiceQuizFormDelegate( choicesAdapter.items = dataset.options?.map { Choice(it, isEnabled = StepQuizFormResolver.isQuizEnabled(state)) } ?: return - if (selectionHelper == null) { + if (choicesAdapter.delegates.isEmpty()) { selectionHelper = if (dataset.isMultipleChoice) { MultipleChoiceSelectionHelper(choicesAdapter) } else { SingleChoiceSelectionHelper(choicesAdapter) } + choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper, onClick = ::handleChoiceClick) } - if (choicesAdapter.delegates.isEmpty()) { - choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper as SelectionHelper, onClick = ::handleChoiceClick) - } - selectionHelper?.reset() + selectionHelper.reset() setChoices(reply?.choices, submission?.status, submission?.feedback as? ChoiceFeedback) } @@ -75,10 +73,10 @@ class ChoiceQuizFormDelegate( private fun handleChoiceClick(choice: Choice) { when (selectionHelper) { is SingleChoiceSelectionHelper -> { - selectionHelper?.select(choicesAdapter.items.indexOf(choice)) + selectionHelper.select(choicesAdapter.items.indexOf(choice)) } is MultipleChoiceSelectionHelper -> { - selectionHelper?.toggle(choicesAdapter.items.indexOf(choice)) + selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) } } } @@ -87,7 +85,7 @@ class ChoiceQuizFormDelegate( if (choices == null) return (0 until choices.size).forEach { pos -> if (choices[pos]) { - selectionHelper?.select(pos) + selectionHelper.select(pos) choicesAdapter.items[pos].apply { correct = when (status) { Submission.Status.CORRECT -> true diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index 688c2e3786..051a7520cc 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -79,7 +79,7 @@ class ChoiceStepQuizFragment : Fragment(), StepQuizView { viewStateDelegate = ViewStateDelegate() viewStateDelegate.addState() viewStateDelegate.addState(stepQuizProgress) - viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, choices_recycler, stepQuizDescription, stepQuizAction) + viewStateDelegate.addState(stepQuizDiscountingPolicy, stepQuizFeedbackBlocks, choicesRecycler, stepQuizDescription, stepQuizAction) viewStateDelegate.addState(stepQuizNetworkError) stepQuizNetworkError.tryAgain.setOnClickListener { presenter.onStepData(stepWrapper, lessonData, forceUpdate = true) } diff --git a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt similarity index 65% rename from app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt rename to app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt index 7f29023e1a..58d5a92607 100644 --- a/app/src/main/java/org/stepic/droid/ui/custom/QuizItemView.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt @@ -1,20 +1,20 @@ -package org.stepic.droid.ui.custom +package org.stepik.android.view.step_quiz_choice.ui.view import android.content.Context import android.util.AttributeSet import android.view.MotionEvent -import android.view.ViewConfiguration -import android.webkit.WebView import android.widget.FrameLayout import kotlinx.android.synthetic.main.latex_supportabe_enhanced_view.view.* -import timber.log.Timber -import java.util.* +import org.stepic.droid.R +import org.stepic.droid.ui.custom.ProgressLatexView /** * Custom item view to infer clicks on the item and scrolling of the webview inside the item */ class QuizItemView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { companion object { @@ -22,26 +22,24 @@ class QuizItemView @JvmOverloads constructor( } private var startClickTime: Long = 0 + private var clickDuration: Long = 0 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { - val webview = getChildAt(1) as ProgressLatexView + val webview = findViewById(R.id.itemChoiceLatex) val dispatched = webview.webView.dispatchTouchEvent(ev) if (ev?.action == MotionEvent.ACTION_DOWN) { - startClickTime = Calendar.getInstance().timeInMillis + startClickTime = ev.eventTime } if (ev?.action == MotionEvent.ACTION_UP) { - onTouchEvent(ev) + clickDuration = ev.eventTime - ev.downTime } super.onTouchEvent(ev) return dispatched } - override fun onTouchEvent(event: MotionEvent?): Boolean { - return false - } + override fun onTouchEvent(event: MotionEvent?): Boolean = false override fun performClick(): Boolean { - val clickDuration = Calendar.getInstance().timeInMillis - startClickTime if (clickDuration < MAX_CLICK_DURATION) { return super.performClick() } diff --git a/app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml similarity index 53% rename from app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml rename to app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml index 52ecd92e10..e78e061a0f 100644 --- a/app/src/main/res/drawable-v21/bg_choice_checked_ripple.xml +++ b/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml @@ -3,16 +3,16 @@ android:color="?android:colorControlHighlight"> - - + + - + android:width="@dimen/choice_option_stroke_width" + android:color="@color/step_quiz_choice_checked_border" /> +
\ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml similarity index 59% rename from app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml rename to app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml index 5ed2c97d43..39def0e775 100644 --- a/app/src/main/res/drawable-v21/bg_choice_not_checked_ripple.xml +++ b/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml @@ -4,15 +4,15 @@ - + - + android:width="@dimen/choice_option_stroke_width" + android:color="@color/step_quiz_choice_not_checked_border" /> + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_quiz_item.xml b/app/src/main/res/drawable/bg_choice_quiz_item.xml deleted file mode 100644 index ec066df18c..0000000000 --- a/app/src/main/res/drawable/bg_choice_quiz_item.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml b/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml index 6f88778fe5..4c8bead25a 100644 --- a/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml +++ b/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_checked_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml similarity index 81% rename from app/src/main/res/drawable/bg_choice_checked_ripple.xml rename to app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml index a3ed373d3f..f187abaf6f 100644 --- a/app/src/main/res/drawable/bg_choice_checked_ripple.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml @@ -4,7 +4,7 @@ + android:color="@color/step_quiz_choice_checked_border" /> diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml new file mode 100644 index 0000000000..09d837e931 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_choice_not_checked_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml similarity index 50% rename from app/src/main/res/drawable/bg_choice_not_checked_ripple.xml rename to app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml index b0e37261b7..88600fd1c5 100644 --- a/app/src/main/res/drawable/bg_choice_not_checked_ripple.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml @@ -3,9 +3,9 @@ - + android:width="@dimen/choice_option_stroke_width" + android:color="@color/step_quiz_choice_not_checked_border" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/item_choice_quiz.xml b/app/src/main/res/layout/item_step_quiz_choice.xml similarity index 88% rename from app/src/main/res/layout/item_choice_quiz.xml rename to app/src/main/res/layout/item_step_quiz_choice.xml index 256b1d246d..e745d5b2b2 100644 --- a/app/src/main/res/layout/item_choice_quiz.xml +++ b/app/src/main/res/layout/item_step_quiz_choice.xml @@ -6,14 +6,14 @@ android:layout_marginBottom="8dp" android:orientation="vertical"> - - - + #26ff7965 - #ffffff - #e8eafa - #e7f7e7 - #ffebe7 - - #888888 - @color/color_violet_1 - #64cc64 - #ff7965 + #ffffff + #e8eafa + #e7f7e7 + #ffebe7 + + #888888 + @color/color_violet_1 + #64cc64 + #ff7965 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 755e32b431..0d8ccf7387 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -226,4 +226,5 @@ 1dp + 8dp diff --git a/model/src/main/java/org/stepik/android/model/Submission.kt b/model/src/main/java/org/stepik/android/model/Submission.kt index f7697a78a7..85fef23d24 100644 --- a/model/src/main/java/org/stepik/android/model/Submission.kt +++ b/model/src/main/java/org/stepik/android/model/Submission.kt @@ -33,12 +33,6 @@ class Submission( val reply: Reply? // this virtual property allows to work with reply like it regular class field without additional wrapper get() = replyWrapper?.reply -// @SerializedName("feedback") -// private val feedBackWrapper: FeedBackWrapper? = feedback?.let(::FeedBackWrapper) -// -// val feedback: Feedback? -// get() = feedBackWrapper?.feedback - enum class Status(val scope: String) { @SerializedName("correct") CORRECT("correct"), From c75f92417786b721aec5ab1ff2637400d3012c1e Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 11:39:12 +0300 Subject: [PATCH 094/108] naming changes and clean up --- .../view/step_quiz_choice/ui/view/QuizItemView.kt | 4 ---- .../bg_step_quiz_choice_checked_ripple.xml | 6 +++--- .../bg_step_quiz_choice_not_checked_ripple.xml | 6 +++--- .../main/res/drawable/bg_step_quiz_choice_item.xml | 12 ++++++------ .../bg_step_quiz_choice_not_checked_ripple.xml | 4 ++-- app/src/main/res/layout/layout_step_quiz_choice.xml | 2 +- app/src/main/res/values/dimens.xml | 4 ++-- .../main/java/org/stepik/android/model/Submission.kt | 2 +- 8 files changed, 18 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt index 58d5a92607..ca46cf215c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/view/QuizItemView.kt @@ -21,15 +21,11 @@ class QuizItemView @JvmOverloads constructor( const val MAX_CLICK_DURATION = 200 } - private var startClickTime: Long = 0 private var clickDuration: Long = 0 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { val webview = findViewById(R.id.itemChoiceLatex) val dispatched = webview.webView.dispatchTouchEvent(ev) - if (ev?.action == MotionEvent.ACTION_DOWN) { - startClickTime = ev.eventTime - } if (ev?.action == MotionEvent.ACTION_UP) { clickDuration = ev.eventTime - ev.downTime } diff --git a/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml index e78e061a0f..21a6e3b98f 100644 --- a/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml +++ b/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml @@ -4,15 +4,15 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml index 39def0e775..3d95724977 100644 --- a/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml +++ b/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml @@ -4,15 +4,15 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml index 09d837e931..d7b0e0eb11 100644 --- a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -61,7 +61,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml index 88600fd1c5..d1a8a46faf 100644 --- a/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml @@ -3,9 +3,9 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_step_quiz_choice.xml b/app/src/main/res/layout/layout_step_quiz_choice.xml index 2f9bb10cf9..798eb916e5 100644 --- a/app/src/main/res/layout/layout_step_quiz_choice.xml +++ b/app/src/main/res/layout/layout_step_quiz_choice.xml @@ -11,9 +11,9 @@ android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="12dp" - android:paddingBottom="12dp" android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" app:layout_constraintVertical_bias="0" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 0d8ccf7387..626fc3cd22 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -225,6 +225,6 @@ 48dp - 1dp - 8dp + 1dp + 8dp diff --git a/model/src/main/java/org/stepik/android/model/Submission.kt b/model/src/main/java/org/stepik/android/model/Submission.kt index 85fef23d24..654421dd44 100644 --- a/model/src/main/java/org/stepik/android/model/Submission.kt +++ b/model/src/main/java/org/stepik/android/model/Submission.kt @@ -33,7 +33,7 @@ class Submission( val reply: Reply? // this virtual property allows to work with reply like it regular class field without additional wrapper get() = replyWrapper?.reply - enum class Status(val scope: String) { + enum class Status(val scope: String) { @SerializedName("correct") CORRECT("correct"), From 3793b0332189ead3e81c7250ae957cb224c95eb8 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 13:03:32 +0300 Subject: [PATCH 095/108] disappearing recycler items fix --- .../step_quiz_choice/model/Choice.kt | 2 +- .../ui/delegate/ChoiceQuizFormDelegate.kt | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt index 60d26df024..ddb53fc0ba 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt @@ -1,7 +1,7 @@ package org.stepik.android.presentation.step_quiz_choice.model data class Choice( - val option: String, + var option: String, var correct: Boolean? = null, var feedback: String? = null, var isEnabled: Boolean = false diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 48ea7d8482..1a5cbabb56 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -47,7 +47,11 @@ class ChoiceQuizFormDelegate( val reply = submission?.reply - choicesAdapter.items = dataset.options?.map { Choice(it, isEnabled = StepQuizFormResolver.isQuizEnabled(state)) } ?: return + if (choicesAdapter.items.isEmpty()) { + choicesAdapter.items = dataset.options?.map { Choice(it, isEnabled = StepQuizFormResolver.isQuizEnabled(state)) } ?: return + } else { + setOptions(dataset.options, StepQuizFormResolver.isQuizEnabled(state)) + } if (choicesAdapter.delegates.isEmpty()) { selectionHelper = if (dataset.isMultipleChoice) { @@ -62,7 +66,7 @@ class ChoiceQuizFormDelegate( } override fun createReply(): ReplyResult { - val choices = (0 until choicesAdapter.itemCount).map { selectionHelper?.isSelected(it) as Boolean } + val choices = (0 until choicesAdapter.itemCount).map { selectionHelper.isSelected(it) } return if (choices.contains(true)) { ReplyResult.Success(Reply(choices = choices)) } else { @@ -81,6 +85,24 @@ class ChoiceQuizFormDelegate( } } + private fun setOptions(receivedOptions: List?, isQuizEnabled: Boolean) { + if (receivedOptions == null) return + + val currentOptions = choicesAdapter.items.map { it.option } + + if (currentOptions != receivedOptions) { + (0 until choicesAdapter.itemCount).forEach { + choicesAdapter.items[it].apply { + option = receivedOptions[it] + correct = null + feedback = null + isEnabled = isQuizEnabled + } + } + choicesAdapter.notifyDataSetChanged() + } + } + private fun setChoices(choices: List?, status: Submission.Status?, choiceFeedback: ChoiceFeedback?) { if (choices == null) return (0 until choices.size).forEach { pos -> From c9703d67770b788efce4fc298d57e3bdb80bc621 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 14:13:49 +0300 Subject: [PATCH 096/108] use single ripple in layer list --- .../bg_step_quiz_choice_checked_ripple.xml | 18 ------- ...bg_step_quiz_choice_not_checked_ripple.xml | 18 ------- .../bg_step_quiz_choice_checked_ripple.xml | 11 ---- .../res/drawable/bg_step_quiz_choice_item.xml | 54 ++++++++++--------- ...bg_step_quiz_choice_not_checked_ripple.xml | 11 ---- .../drawable/bg_step_quiz_choice_ripple.xml | 13 +++++ 6 files changed, 41 insertions(+), 84 deletions(-) delete mode 100644 app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml delete mode 100644 app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml delete mode 100644 app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml delete mode 100644 app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml create mode 100644 app/src/main/res/drawable/bg_step_quiz_choice_ripple.xml diff --git a/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml deleted file mode 100644 index 21a6e3b98f..0000000000 --- a/app/src/main/res/drawable-v21/bg_step_quiz_choice_checked_ripple.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml b/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml deleted file mode 100644 index 3d95724977..0000000000 --- a/app/src/main/res/drawable-v21/bg_step_quiz_choice_not_checked_ripple.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml deleted file mode 100644 index f187abaf6f..0000000000 --- a/app/src/main/res/drawable/bg_step_quiz_choice_checked_ripple.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml index d7b0e0eb11..e5b9ff1118 100644 --- a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml @@ -1,42 +1,39 @@ - - - - - - - - - - + + + + + + - + + - - - - - - - - - - + + + + + + + + @@ -46,6 +43,7 @@ + @@ -55,13 +53,17 @@ + - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml deleted file mode 100644 index d1a8a46faf..0000000000 --- a/app/src/main/res/drawable/bg_step_quiz_choice_not_checked_ripple.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_ripple.xml b/app/src/main/res/drawable/bg_step_quiz_choice_ripple.xml new file mode 100644 index 0000000000..c95711d682 --- /dev/null +++ b/app/src/main/res/drawable/bg_step_quiz_choice_ripple.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file From 2bcfe46075ebb24f1e92c6205f3124c7ef67a906 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 15:29:22 +0300 Subject: [PATCH 097/108] refactor choice selection --- .../step_quiz_choice/model/Choice.kt | 15 ++-- .../ui/delegate/ChoiceQuizFormDelegate.kt | 71 ++++++++----------- 2 files changed, 38 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt index ddb53fc0ba..8044fdcfa3 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt +++ b/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt @@ -1,8 +1,13 @@ package org.stepik.android.presentation.step_quiz_choice.model +import ru.nobird.android.core.model.Identifiable + data class Choice( - var option: String, - var correct: Boolean? = null, - var feedback: String? = null, - var isEnabled: Boolean = false -) \ No newline at end of file + val option: String, + val correct: Boolean? = null, + val feedback: String? = null, + val isEnabled: Boolean = false +) : Identifiable { + override val id: String + get() = option +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index 1a5cbabb56..de199b6857 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -47,22 +47,17 @@ class ChoiceQuizFormDelegate( val reply = submission?.reply - if (choicesAdapter.items.isEmpty()) { - choicesAdapter.items = dataset.options?.map { Choice(it, isEnabled = StepQuizFormResolver.isQuizEnabled(state)) } ?: return - } else { - setOptions(dataset.options, StepQuizFormResolver.isQuizEnabled(state)) - } - - if (choicesAdapter.delegates.isEmpty()) { - selectionHelper = if (dataset.isMultipleChoice) { - MultipleChoiceSelectionHelper(choicesAdapter) - } else { - SingleChoiceSelectionHelper(choicesAdapter) - } + if (!::selectionHelper.isInitialized) { + selectionHelper = + if (dataset.isMultipleChoice) { + MultipleChoiceSelectionHelper(choicesAdapter) + } else { + SingleChoiceSelectionHelper(choicesAdapter) + } choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper, onClick = ::handleChoiceClick) } selectionHelper.reset() - setChoices(reply?.choices, submission?.status, submission?.feedback as? ChoiceFeedback) + mapChoices(dataset.options ?: emptyList(), reply?.choices, submission, StepQuizFormResolver.isQuizEnabled(state)) } override fun createReply(): ReplyResult { @@ -85,38 +80,28 @@ class ChoiceQuizFormDelegate( } } - private fun setOptions(receivedOptions: List?, isQuizEnabled: Boolean) { - if (receivedOptions == null) return - - val currentOptions = choicesAdapter.items.map { it.option } + private fun mapChoices(options: List, choices: List?, submission: Submission?, isQuizEnabled: Boolean) { + val feedback = submission?.feedback as? ChoiceFeedback - if (currentOptions != receivedOptions) { - (0 until choicesAdapter.itemCount).forEach { - choicesAdapter.items[it].apply { - option = receivedOptions[it] - correct = null - feedback = null - isEnabled = isQuizEnabled - } - } - choicesAdapter.notifyDataSetChanged() - } - } - - private fun setChoices(choices: List?, status: Submission.Status?, choiceFeedback: ChoiceFeedback?) { - if (choices == null) return - (0 until choices.size).forEach { pos -> - if (choices[pos]) { - selectionHelper.select(pos) - choicesAdapter.items[pos].apply { - correct = when (status) { - Submission.Status.CORRECT -> true - Submission.Status.WRONG -> false - else -> null + choicesAdapter.items = + options.mapIndexed { i, option -> + val isCorrect = + if (choices?.getOrNull(i) == true) { + selectionHelper.select(i) + when (submission?.status) { + Submission.Status.CORRECT -> true + Submission.Status.WRONG -> false + else -> null + } + } else { + null } - } + Choice( + option = option, + feedback = feedback?.optionsFeedback?.getOrNull(i), + correct = isCorrect, + isEnabled = isQuizEnabled + ) } - choicesAdapter.items[pos].feedback = choiceFeedback?.optionsFeedback?.get(pos) ?: "" - } } } \ No newline at end of file From 8641e0a821a782c54063fdc511daa38cbb17cdb0 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 18:04:44 +0300 Subject: [PATCH 098/108] transfer Choice model to view package layer --- .../{presentation => view}/step_quiz_choice/model/Choice.kt | 2 +- .../view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt | 2 +- .../view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename app/src/main/java/org/stepik/android/{presentation => view}/step_quiz_choice/model/Choice.kt (81%) diff --git a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/model/Choice.kt similarity index 81% rename from app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt rename to app/src/main/java/org/stepik/android/view/step_quiz_choice/model/Choice.kt index 8044fdcfa3..673de21fbc 100644 --- a/app/src/main/java/org/stepik/android/presentation/step_quiz_choice/model/Choice.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/model/Choice.kt @@ -1,4 +1,4 @@ -package org.stepik.android.presentation.step_quiz_choice.model +package org.stepik.android.view.step_quiz_choice.model import ru.nobird.android.core.model.Identifiable diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index fc9d89f8d6..a29fb73478 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -7,7 +7,7 @@ import kotlinx.android.synthetic.main.item_step_quiz_choice.view.* import org.stepic.droid.R import org.stepic.droid.fonts.FontType import org.stepic.droid.fonts.FontsProvider -import org.stepik.android.presentation.step_quiz_choice.model.Choice +import org.stepik.android.view.step_quiz_choice.model.Choice import org.stepik.android.view.step_quiz_choice.ui.delegate.LayerListDrawableDelegate import ru.nobird.android.ui.adapterdelegatessupport.AdapterDelegate import ru.nobird.android.ui.adapterdelegatessupport.DelegateViewHolder diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt index de199b6857..9dfe9087ed 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt @@ -11,7 +11,7 @@ import org.stepik.android.model.Submission import org.stepik.android.model.feedback.ChoiceFeedback import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult -import org.stepik.android.presentation.step_quiz_choice.model.Choice +import org.stepik.android.view.step_quiz_choice.model.Choice import org.stepik.android.view.step_quiz.resolver.StepQuizFormResolver import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate From 6d46e5198798cb2fa541dfae666d0e9db763d1ec Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 18:35:10 +0300 Subject: [PATCH 099/108] moved code from delegate to mapper --- .../mapper/ChoiceStepQuizOptionsMapper.kt | 40 +++++++++++++++++ ...egate.kt => ChoiceStepQuizFormDelegate.kt} | 43 ++++++------------- .../ui/fragment/ChoiceStepQuizFragment.kt | 4 +- 3 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt rename app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/{ChoiceQuizFormDelegate.kt => ChoiceStepQuizFormDelegate.kt} (72%) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt new file mode 100644 index 0000000000..472bbaa9cb --- /dev/null +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt @@ -0,0 +1,40 @@ +package org.stepik.android.view.step_quiz_choice.mapper + +import org.stepik.android.model.Submission +import org.stepik.android.model.feedback.ChoiceFeedback +import org.stepik.android.view.step_quiz_choice.model.Choice +import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper + +class ChoiceStepQuizOptionsMapper { + fun mapChoices(options: List, choices: List?, submission: Submission?, isQuizEnabled: Boolean): List { + val feedback = submission?.feedback as? ChoiceFeedback + + return options.mapIndexed { i, option -> + val isCorrect = + if (choices?.getOrNull(i) == true) { + when (submission?.status) { + Submission.Status.CORRECT -> true + Submission.Status.WRONG -> false + else -> null + } + } else { + null + } + Choice( + option = option, + feedback = feedback?.optionsFeedback?.getOrNull(i), + correct = isCorrect, + isEnabled = isQuizEnabled + ) + } + } + + fun mapSelections(choices: List?, selectionHelper: SelectionHelper) { + if (choices == null) return + choices.forEachIndexed { index, choice -> + if (choice) { + selectionHelper.select(index) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt similarity index 72% rename from app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt rename to app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt index 9dfe9087ed..f8069dff40 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt @@ -7,26 +7,26 @@ import kotlinx.android.synthetic.main.layout_step_quiz_choice.view.* import org.stepic.droid.R import org.stepic.droid.fonts.FontsProvider import org.stepik.android.model.Reply -import org.stepik.android.model.Submission -import org.stepik.android.model.feedback.ChoiceFeedback import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.presentation.step_quiz.model.ReplyResult -import org.stepik.android.view.step_quiz_choice.model.Choice import org.stepik.android.view.step_quiz.resolver.StepQuizFormResolver import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFormDelegate +import org.stepik.android.view.step_quiz_choice.mapper.ChoiceStepQuizOptionsMapper +import org.stepik.android.view.step_quiz_choice.model.Choice import org.stepik.android.view.step_quiz_choice.ui.adapter.ChoicesAdapterDelegate import ru.nobird.android.ui.adapterssupport.DefaultDelegateAdapter import ru.nobird.android.ui.adapterssupport.selection.MultipleChoiceSelectionHelper import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper import ru.nobird.android.ui.adapterssupport.selection.SingleChoiceSelectionHelper -class ChoiceQuizFormDelegate( +class ChoiceStepQuizFormDelegate( containerView: View, private val fontsProvider: FontsProvider ) : StepQuizFormDelegate { private val context = containerView.context private val quizDescription = containerView.stepQuizDescription + private val choiceStepQuizOptionsMapper = ChoiceStepQuizOptionsMapper() private var choicesAdapter: DefaultDelegateAdapter = DefaultDelegateAdapter() private lateinit var selectionHelper: SelectionHelper @@ -56,8 +56,16 @@ class ChoiceQuizFormDelegate( } choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper, onClick = ::handleChoiceClick) } + selectionHelper.reset() - mapChoices(dataset.options ?: emptyList(), reply?.choices, submission, StepQuizFormResolver.isQuizEnabled(state)) + + choicesAdapter.items = choiceStepQuizOptionsMapper.mapChoices( + dataset.options ?: emptyList(), + reply?.choices, + submission, + StepQuizFormResolver.isQuizEnabled(state) + ) + choiceStepQuizOptionsMapper.mapSelections(reply?.choices, selectionHelper) } override fun createReply(): ReplyResult { @@ -79,29 +87,4 @@ class ChoiceQuizFormDelegate( } } } - - private fun mapChoices(options: List, choices: List?, submission: Submission?, isQuizEnabled: Boolean) { - val feedback = submission?.feedback as? ChoiceFeedback - - choicesAdapter.items = - options.mapIndexed { i, option -> - val isCorrect = - if (choices?.getOrNull(i) == true) { - selectionHelper.select(i) - when (submission?.status) { - Submission.Status.CORRECT -> true - Submission.Status.WRONG -> false - else -> null - } - } else { - null - } - Choice( - option = option, - feedback = feedback?.optionsFeedback?.getOrNull(i), - correct = isCorrect, - isEnabled = isQuizEnabled - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt index 051a7520cc..758d5f8bcc 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/fragment/ChoiceStepQuizFragment.kt @@ -24,7 +24,7 @@ import org.stepik.android.presentation.step_quiz.StepQuizPresenter import org.stepik.android.presentation.step_quiz.StepQuizView import org.stepik.android.view.step_quiz.ui.delegate.StepQuizDelegate import org.stepik.android.view.step_quiz.ui.delegate.StepQuizFeedbackBlocksDelegate -import org.stepik.android.view.step_quiz_choice.ui.delegate.ChoiceQuizFormDelegate +import org.stepik.android.view.step_quiz_choice.ui.delegate.ChoiceStepQuizFormDelegate import org.stepik.android.view.ui.delegate.ViewStateDelegate import javax.inject.Inject @@ -87,7 +87,7 @@ class ChoiceStepQuizFragment : Fragment(), StepQuizView { stepQuizDelegate = StepQuizDelegate( step = stepWrapper.step, - stepQuizFormDelegate = ChoiceQuizFormDelegate(view, fontsProvider), + stepQuizFormDelegate = ChoiceStepQuizFormDelegate(view, fontsProvider), stepQuizFeedbackBlocksDelegate = StepQuizFeedbackBlocksDelegate(stepQuizFeedbackBlocks, fontsProvider), stepQuizActionButton = stepQuizAction, stepQuizDiscountingPolicy = stepQuizDiscountingPolicy, From 0c09e495e018104775d84506c0e8efc8393d32d7 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 19:01:02 +0300 Subject: [PATCH 100/108] code style fixes --- .../ui/adapter/ChoicesAdapterDelegate.kt | 14 +++++--------- .../ui/delegate/ChoiceStepQuizFormDelegate.kt | 16 +++++++++------- .../ui/delegate/LayerListDrawableDelegate.kt | 7 ++----- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index a29fb73478..e24c71433a 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -56,10 +56,10 @@ class ChoicesAdapterDelegate( override fun onBind(data: Choice) { itemView.itemChoiceContainer.isEnabled = data.isEnabled itemView.isSelected = selectionHelper.isSelected(adapterPosition) - if (data.correct == true) { - itemChoiceCheckmark.visibility = View.VISIBLE + itemChoiceCheckmark.visibility = if (data.correct == true) { + View.VISIBLE } else { - itemChoiceCheckmark.visibility = View.INVISIBLE + View.INVISIBLE } itemChoiceLatex.setAnyText(data.option) layerListDrawableDelegate.showLayer(getItemBackgroundLayer(data)) @@ -82,9 +82,7 @@ class ChoicesAdapterDelegate( private fun getItemBackgroundLayer(data: Choice): Int = if (itemView.isSelected) { when (data.correct) { - true -> { - R.id.correct_layer - } + true -> R.id.correct_layer false -> { if (data.feedback.isNullOrEmpty()) { R.id.incorrect_layer @@ -92,9 +90,7 @@ class ChoicesAdapterDelegate( R.id.incorrect_layer_with_hint } } - else -> { - R.id.checked_layer - } + else -> R.id.checked_layer } } else { if (data.feedback.isNullOrEmpty()) { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt index f8069dff40..2bd8ac44b5 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt @@ -65,7 +65,13 @@ class ChoiceStepQuizFormDelegate( submission, StepQuizFormResolver.isQuizEnabled(state) ) - choiceStepQuizOptionsMapper.mapSelections(reply?.choices, selectionHelper) + reply?.choices?.let { + it.forEachIndexed { index, choice -> + if (choice) { + selectionHelper.select(index) + } + } + } } override fun createReply(): ReplyResult { @@ -79,12 +85,8 @@ class ChoiceStepQuizFormDelegate( private fun handleChoiceClick(choice: Choice) { when (selectionHelper) { - is SingleChoiceSelectionHelper -> { - selectionHelper.select(choicesAdapter.items.indexOf(choice)) - } - is MultipleChoiceSelectionHelper -> { - selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) - } + is SingleChoiceSelectionHelper -> selectionHelper.select(choicesAdapter.items.indexOf(choice)) + is MultipleChoiceSelectionHelper -> selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt index d9dc06dc3a..b0141b4b25 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/LayerListDrawableDelegate.kt @@ -9,11 +9,8 @@ class LayerListDrawableDelegate( fun showLayer(visibleLayerId: Int) { for (layerId in layerIds) { val layer = layers.findDrawableByLayerId(layerId).mutate() - layer.alpha = if (layerId == visibleLayerId) { - 255 - } else { - 0 - } + layer.alpha = + if (layerId == visibleLayerId) 255 else 0 layers.setDrawableByLayerId(layerId, layer) layers.invalidateSelf() } From eb12d2797732fcf3851bcb80790003d9fc869e67 Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 19:02:15 +0300 Subject: [PATCH 101/108] layout paddings and margins fix --- app/src/main/res/layout/item_step_quiz_choice.xml | 3 ++- app/src/main/res/layout/layout_step_quiz_choice.xml | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/item_step_quiz_choice.xml b/app/src/main/res/layout/item_step_quiz_choice.xml index e745d5b2b2..5239cc7447 100644 --- a/app/src/main/res/layout/item_step_quiz_choice.xml +++ b/app/src/main/res/layout/item_step_quiz_choice.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="8dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" android:orientation="vertical"> Date: Thu, 11 Jul 2019 19:07:10 +0300 Subject: [PATCH 102/108] redundant method removed --- .../mapper/ChoiceStepQuizOptionsMapper.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt index 472bbaa9cb..74758a29e9 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/mapper/ChoiceStepQuizOptionsMapper.kt @@ -3,7 +3,6 @@ package org.stepik.android.view.step_quiz_choice.mapper import org.stepik.android.model.Submission import org.stepik.android.model.feedback.ChoiceFeedback import org.stepik.android.view.step_quiz_choice.model.Choice -import ru.nobird.android.ui.adapterssupport.selection.SelectionHelper class ChoiceStepQuizOptionsMapper { fun mapChoices(options: List, choices: List?, submission: Submission?, isQuizEnabled: Boolean): List { @@ -28,13 +27,4 @@ class ChoiceStepQuizOptionsMapper { ) } } - - fun mapSelections(choices: List?, selectionHelper: SelectionHelper) { - if (choices == null) return - choices.forEachIndexed { index, choice -> - if (choice) { - selectionHelper.select(index) - } - } - } } \ No newline at end of file From 53503f2b9baecc2e375030d9d9e2fe4c99b706df Mon Sep 17 00:00:00 2001 From: Rostislav Smirnov Date: Thu, 11 Jul 2019 19:18:30 +0300 Subject: [PATCH 103/108] handle zero options case in dataset --- .../ui/adapter/ChoicesAdapterDelegate.kt | 11 +++++++---- .../ui/delegate/ChoiceStepQuizFormDelegate.kt | 17 ++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index e24c71433a..e25bc130be 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -82,15 +82,18 @@ class ChoicesAdapterDelegate( private fun getItemBackgroundLayer(data: Choice): Int = if (itemView.isSelected) { when (data.correct) { - true -> R.id.correct_layer - false -> { + true -> + R.id.correct_layer + + false -> if (data.feedback.isNullOrEmpty()) { R.id.incorrect_layer } else { R.id.incorrect_layer_with_hint } - } - else -> R.id.checked_layer + + else -> + R.id.checked_layer } } else { if (data.feedback.isNullOrEmpty()) { diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt index 2bd8ac44b5..b0e2b44db3 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt @@ -57,14 +57,14 @@ class ChoiceStepQuizFormDelegate( choicesAdapter += ChoicesAdapterDelegate(fontsProvider, selectionHelper, onClick = ::handleChoiceClick) } - selectionHelper.reset() - choicesAdapter.items = choiceStepQuizOptionsMapper.mapChoices( dataset.options ?: emptyList(), reply?.choices, submission, StepQuizFormResolver.isQuizEnabled(state) ) + + selectionHelper.reset() reply?.choices?.let { it.forEachIndexed { index, choice -> if (choice) { @@ -76,17 +76,20 @@ class ChoiceStepQuizFormDelegate( override fun createReply(): ReplyResult { val choices = (0 until choicesAdapter.itemCount).map { selectionHelper.isSelected(it) } - return if (choices.contains(true)) { - ReplyResult.Success(Reply(choices = choices)) - } else { + return if ((true !in choices && selectionHelper is SingleChoiceSelectionHelper)) { ReplyResult.Error(context.getString(R.string.step_quiz_choice_empty_reply)) + } else { + ReplyResult.Success(Reply(choices = choices)) } } private fun handleChoiceClick(choice: Choice) { when (selectionHelper) { - is SingleChoiceSelectionHelper -> selectionHelper.select(choicesAdapter.items.indexOf(choice)) - is MultipleChoiceSelectionHelper -> selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) + is SingleChoiceSelectionHelper -> + selectionHelper.select(choicesAdapter.items.indexOf(choice)) + + is MultipleChoiceSelectionHelper -> + selectionHelper.toggle(choicesAdapter.items.indexOf(choice)) } } } \ No newline at end of file From bf33dfcd38c8a0006fdd440612fd54a8b20097cb Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 12 Jul 2019 11:12:50 +0300 Subject: [PATCH 104/108] pre merge fixes --- .../ui/adapter/ChoicesAdapterDelegate.kt | 8 ++++--- .../ui/delegate/ChoiceStepQuizFormDelegate.kt | 12 +++++++++- .../res/drawable/bg_step_quiz_choice_item.xml | 8 +++---- ... => bg_step_quiz_choice_item_feedback.xml} | 5 ++-- ...rk.xml => ic_step_quiz_choice_correct.xml} | 0 ...tion.xml => ic_step_quiz_choice_wrong.xml} | 0 .../main/res/layout/item_step_quiz_choice.xml | 23 ++++--------------- .../res/layout/layout_step_quiz_choice.xml | 4 +--- app/src/main/res/values-ru/strings.xml | 3 ++- app/src/main/res/values/colors.xml | 4 ++-- app/src/main/res/values/strings.xml | 3 ++- 11 files changed, 34 insertions(+), 36 deletions(-) rename app/src/main/res/drawable/{bg_compound_step_quiz_feedback_tip.xml => bg_step_quiz_choice_item_feedback.xml} (53%) rename app/src/main/res/drawable/{ic_correct_checkmark.xml => ic_step_quiz_choice_correct.xml} (100%) rename app/src/main/res/drawable/{ic_incorrect_exclamation.xml => ic_step_quiz_choice_wrong.xml} (100%) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt index e25bc130be..baac6dd056 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/adapter/ChoicesAdapterDelegate.kt @@ -31,7 +31,6 @@ class ChoicesAdapterDelegate( private val itemChoiceContainer = root.itemChoiceContainer private val itemChoiceCheckmark = root.itemChoiceCheckmark private val itemChoiceLatex = root.itemChoiceLatex - private val itemChoiceFeedbackContainer = root.itemChoiceFeedbackContainer private val itemChoiceFeedback = root.itemChoiceFeedback private val layerListDrawableDelegate: LayerListDrawableDelegate @@ -51,6 +50,9 @@ class ChoicesAdapterDelegate( R.id.incorrect_layer_with_hint ), itemChoiceContainer.background.mutate() as LayerDrawable) + + itemChoiceFeedback.setTextSize(14f) + itemChoiceFeedback.setBackgroundResource(R.drawable.bg_step_quiz_choice_item_feedback) } override fun onBind(data: Choice) { @@ -68,9 +70,9 @@ class ChoicesAdapterDelegate( private fun bindHint(data: Choice) { if (data.feedback.isNullOrEmpty()) { - itemChoiceFeedbackContainer.visibility = View.GONE + itemChoiceFeedback.visibility = View.GONE } else { - itemChoiceFeedbackContainer.visibility = View.VISIBLE + itemChoiceFeedback.visibility = View.VISIBLE itemChoiceFeedback.setPlainOrLaTeXTextWithCustomFontColored( data.feedback, fontsProvider.provideFontPath(FontType.mono), R.color.new_accent_color, diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt index b0e2b44db3..607fee50e9 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_choice/ui/delegate/ChoiceStepQuizFormDelegate.kt @@ -1,5 +1,6 @@ package org.stepik.android.view.step_quiz_choice.ui.delegate +import android.support.annotation.StringRes import android.support.v7.widget.LinearLayoutManager import android.view.View import kotlinx.android.synthetic.main.fragment_step_quiz.view.* @@ -35,13 +36,22 @@ class ChoiceStepQuizFormDelegate( itemAnimator = null adapter = choicesAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + isNestedScrollingEnabled = false } - quizDescription.setText(R.string.step_quiz_choice_description) } override fun setState(state: StepQuizView.State.AttemptLoaded) { val dataset = state.attempt.dataset ?: return + @StringRes + val descriptionRes = + if (dataset.isMultipleChoice) { + R.string.step_quiz_choice_description_multiple + } else { + R.string.step_quiz_choice_description_single + } + quizDescription.setText(descriptionRes) + val submission = (state.submissionState as? StepQuizView.SubmissionState.Loaded) ?.submission diff --git a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml index e5b9ff1118..c8f603c82c 100644 --- a/app/src/main/res/drawable/bg_step_quiz_choice_item.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_item.xml @@ -46,20 +46,20 @@ - + + android:color="@color/step_quiz_choice_wrong_border" /> - + + android:color="@color/step_quiz_choice_wrong_border" /> diff --git a/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml b/app/src/main/res/drawable/bg_step_quiz_choice_item_feedback.xml similarity index 53% rename from app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml rename to app/src/main/res/drawable/bg_step_quiz_choice_item_feedback.xml index 4c8bead25a..6eba819f34 100644 --- a/app/src/main/res/drawable/bg_compound_step_quiz_feedback_tip.xml +++ b/app/src/main/res/drawable/bg_step_quiz_choice_item_feedback.xml @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_correct_checkmark.xml b/app/src/main/res/drawable/ic_step_quiz_choice_correct.xml similarity index 100% rename from app/src/main/res/drawable/ic_correct_checkmark.xml rename to app/src/main/res/drawable/ic_step_quiz_choice_correct.xml diff --git a/app/src/main/res/drawable/ic_incorrect_exclamation.xml b/app/src/main/res/drawable/ic_step_quiz_choice_wrong.xml similarity index 100% rename from app/src/main/res/drawable/ic_incorrect_exclamation.xml rename to app/src/main/res/drawable/ic_step_quiz_choice_wrong.xml diff --git a/app/src/main/res/layout/item_step_quiz_choice.xml b/app/src/main/res/layout/item_step_quiz_choice.xml index 5239cc7447..038d2774bd 100644 --- a/app/src/main/res/layout/item_step_quiz_choice.xml +++ b/app/src/main/res/layout/item_step_quiz_choice.xml @@ -19,7 +19,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_gravity="center_vertical|end" - app:srcCompat="@drawable/ic_correct_checkmark"/> + app:srcCompat="@drawable/ic_step_quiz_choice_correct"/> - - - - + android:padding="16dp" /> diff --git a/app/src/main/res/layout/layout_step_quiz_choice.xml b/app/src/main/res/layout/layout_step_quiz_choice.xml index fbeca8e31b..56be8a9e06 100644 --- a/app/src/main/res/layout/layout_step_quiz_choice.xml +++ b/app/src/main/res/layout/layout_step_quiz_choice.xml @@ -2,7 +2,6 @@ \ No newline at end of file + app:layout_constraintBottom_toTopOf="@id/stepQuizFeedbackBlocks" /> \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d693990009..62b7fac1fd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -727,7 +727,8 @@ Введите математическую формулу Напишите текст (строку) Напишите эссе - Выберите один или несколько элементов + Выберите один элемент + Выберите один или несколько элементов Ответ не может быть пустым Выберите вариант diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 55e0c0f7cc..2f23a30558 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -213,10 +213,10 @@ #ffffff #e8eafa #e7f7e7 - #ffebe7 + #ffebe7 #888888 @color/color_violet_1 #64cc64 - #ff7965 + #ff7965 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0b05fcbb5..16733002ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -757,7 +757,8 @@ Enter a math formula Write an answer in form of a text (string) Write an essay - Choose one or multiple options + Choose one option + Choose one or multiple options An answer cannot be empty Choose an option From d66c43ecedadef6ff1e237396377be3c193b82d8 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 12 Jul 2019 11:26:35 +0300 Subject: [PATCH 105/108] inc version to 2028 --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index e9c1522ae7..807aa13652 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,6 +1,6 @@ ext.versions = [ - code : 2027, - name : '1.86', + code : 2028, + name : '1.87', minSdk : 15, targetSdk : 26, From 45d81328ea92cdaeaeb2e27c62006f1c049f1638 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 12 Jul 2019 18:46:58 +0300 Subject: [PATCH 106/108] APPS-2373: fix clickable links in hint --- .../view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt index 31189c837f..d3a00e27ab 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz/ui/delegate/StepQuizFeedbackBlocksDelegate.kt @@ -52,6 +52,7 @@ class StepQuizFeedbackBlocksDelegate( stepQuizFeedbackHint.setTextSize(14f) stepQuizFeedbackHint.setBackgroundResource(R.drawable.bg_step_quiz_hint) + stepQuizFeedbackHint.setTextIsSelectable(false) } fun setState(state: StepQuizFeedbackState) { From d4c1a1bfdc836d7797a641cd1ae3d6c4ffe09d6a Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Fri, 12 Jul 2019 18:54:07 +0300 Subject: [PATCH 107/108] inc version to 2029 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 807aa13652..0c2a241bb1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,5 +1,5 @@ ext.versions = [ - code : 2028, + code : 2029, name : '1.87', minSdk : 15, From 26e83636b0383bba41918599448fc2d875ba4bf7 Mon Sep 17 00:00:00 2001 From: Ruslan Davletshin Date: Mon, 15 Jul 2019 19:07:09 +0300 Subject: [PATCH 108/108] add new bracket type to latex --- app/src/main/java/org/stepic/droid/util/HtmlHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/stepic/droid/util/HtmlHelper.java b/app/src/main/java/org/stepic/droid/util/HtmlHelper.java index 0e12fac4c5..50c39daad7 100644 --- a/app/src/main/java/org/stepic/droid/util/HtmlHelper.java +++ b/app/src/main/java/org/stepic/droid/util/HtmlHelper.java @@ -63,7 +63,7 @@ public static boolean isForWebView(@NotNull String text) { public static boolean hasLaTeX(String textString) { - return textString.contains("$") || textString.contains("\\[") || textString.contains("math-tex"); + return textString.contains("$") || textString.contains("\\[") || textString.contains("math-tex") || textString.contains("\\("); } private static boolean hasKotlinRunnableSample(String text) {