diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index d580de4118..1a685f8fee 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -18,12 +18,12 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import by.kirich1409.viewbindingdelegate.viewBinding +import coil.ImageLoader import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.hyperskill.app.android.HyperskillApp import org.hyperskill.app.android.R import org.hyperskill.app.android.core.extensions.argument import org.hyperskill.app.android.core.view.ui.fragment.parentOfType -import org.hyperskill.app.android.core.view.ui.fragment.setChildFragment import org.hyperskill.app.android.core.view.ui.navigation.requireRouter import org.hyperskill.app.android.databinding.FragmentStepQuizBinding import org.hyperskill.app.android.databinding.LayoutStepQuizDescriptionBinding @@ -40,7 +40,7 @@ import org.hyperskill.app.android.step_quiz.view.delegate.StepQuizFormDelegate import org.hyperskill.app.android.step_quiz.view.factory.StepQuizViewStateDelegateFactory import org.hyperskill.app.android.step_quiz.view.mapper.StepQuizFeedbackMapper import org.hyperskill.app.android.step_quiz.view.model.StepQuizFeedbackState -import org.hyperskill.app.android.step_quiz_hints.fragment.StepQuizHintsFragment +import org.hyperskill.app.android.step_quiz_hints.delegate.StepQuizHintsDelegate import org.hyperskill.app.android.step_quiz_parsons.view.dialog.ParsonsStepQuizOnboardingBottomSheetDialogFragment import org.hyperskill.app.android.view.base.ui.extension.snackbar import org.hyperskill.app.step.domain.model.BlockName @@ -56,6 +56,7 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizViewModel import org.hyperskill.app.step_quiz.view.mapper.StepQuizStatsTextMapper import org.hyperskill.app.step_quiz.view.mapper.StepQuizTitleMapper import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.step_quiz_hints.view.mapper.StepQuizHintsViewStateMapper import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.showIfNotExists import ru.nobird.app.presentation.redux.container.ReduxView @@ -67,10 +68,6 @@ abstract class DefaultStepQuizFragment : MenuProvider, ParsonsStepQuizOnboardingBottomSheetDialogFragment.Callback { - companion object { - private const val STEP_HINTS_FRAGMENT_TAG = "step_hints" - } - private lateinit var viewModelFactory: ViewModelProvider.Factory private val stepQuizViewModel: StepQuizViewModel by viewModels { viewModelFactory } @@ -83,6 +80,8 @@ abstract class DefaultStepQuizFragment : private var stepQuizFormDelegate: StepQuizFormDelegate? = null private var stepQuizButtonsViewStateDelegate: ViewStateDelegate? = null + private var stepQuizHintsDelegate: StepQuizHintsDelegate? = null + private var stepQuizStatsTextMapper: StepQuizStatsTextMapper? = null private var stepQuizTitleMapper: StepQuizTitleMapper? = null private val stepQuizFeedbackMapper by lazy(LazyThreadSafetyMode.NONE) { @@ -103,6 +102,10 @@ abstract class DefaultStepQuizFragment : private var isKeyboardShown: Boolean = false + private val svgImageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) { + HyperskillApp.graph().imageLoadingComponent.imageLoader + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) injectComponent() @@ -139,6 +142,13 @@ abstract class DefaultStepQuizFragment : stepQuizFormDelegate = createStepQuizFormDelegate().also { delegate -> delegate.customizeSubmissionButton(viewBinding.stepQuizButtons.stepQuizSubmitButton) } + stepQuizHintsDelegate = StepQuizHintsDelegate( + binding = viewBinding.stepQuizHints, + imageLoader = svgImageLoader, + onNewMessage = { message -> + stepQuizViewModel.onNewMessage(StepQuizFeature.Message.StepQuizHintsMessage(message)) + } + ) renderStatistics(viewBinding.stepQuizStatistics, step) initButtonsViewStateDelegate() setupQuizButtons() @@ -199,6 +209,7 @@ abstract class DefaultStepQuizFragment : stepQuizButtonsViewStateDelegate = null stepQuizFeedbackBlocksDelegate = null stepQuizFormDelegate = null + stepQuizHintsDelegate = null } override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { @@ -278,6 +289,9 @@ abstract class DefaultStepQuizFragment : ParsonsStepQuizOnboardingBottomSheetDialogFragment.newInstance() .showIfNotExists(childFragmentManager, ParsonsStepQuizOnboardingBottomSheetDialogFragment.TAG) } + is StepQuizFeature.Action.ViewAction.StepQuizHintsViewAction -> { + stepQuizHintsDelegate?.onAction(action.viewAction) + } } } @@ -315,7 +329,6 @@ abstract class DefaultStepQuizFragment : stepQuizFeedbackBlocksDelegate?.setState(StepQuizFeedbackState.Unsupported) } is StepQuizFeature.StepQuizState.AttemptLoaded -> { - setStepHintsFragment(step) renderAttemptLoaded(stepQuizState) } else -> { @@ -324,6 +337,7 @@ abstract class DefaultStepQuizFragment : } renderTheoryButton(state.stepQuizState) + renderHints(state.stepQuizHintsState) onNewState(state) } @@ -386,14 +400,9 @@ abstract class DefaultStepQuizFragment : } } - private fun setStepHintsFragment(step: Step) { - val isFeatureEnabled = StepQuizHintsFeature.isHintsFeatureAvailable(step) - viewBinding.stepQuizHints.isVisible = isFeatureEnabled - if (isFeatureEnabled) { - setChildFragment(R.id.stepQuizHints, STEP_HINTS_FRAGMENT_TAG) { - StepQuizHintsFragment.newInstance(stepRoute, step) - } - } + private fun renderHints(state: StepQuizHintsFeature.State) { + val viewState = StepQuizHintsViewStateMapper.mapState(state) + stepQuizHintsDelegate?.render(requireContext(), viewState) } final override fun render(isPracticingLoading: Boolean) { diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/delegate/StepQuizHintsDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/delegate/StepQuizHintsDelegate.kt index b23ff823d5..8dca1eb629 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/delegate/StepQuizHintsDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/delegate/StepQuizHintsDelegate.kt @@ -2,16 +2,21 @@ package org.hyperskill.app.android.step_quiz_hints.delegate import android.content.Context import android.text.method.LinkMovementMethod +import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import coil.ImageLoader import coil.load import coil.transform.CircleCropTransformation import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.hyperskill.app.R import org.hyperskill.app.android.databinding.LayoutStepQuizHintsBinding import org.hyperskill.app.reactions.domain.model.ReactionType import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.setTextIfChanged +import ru.nobird.android.view.base.ui.extension.snackbar class StepQuizHintsDelegate( private val binding: LayoutStepQuizHintsBinding, @@ -21,7 +26,7 @@ class StepQuizHintsDelegate( init { with(binding) { with(stepQuizSeeHintsButton.root) { - setText(org.hyperskill.app.R.string.step_quiz_hints_show_button_text) + setText(R.string.step_quiz_hints_show_button_text) setOnClickListener { onNewMessage(StepQuizHintsFeature.Message.LoadHintButtonClicked) } @@ -31,7 +36,7 @@ class StepQuizHintsDelegate( stepQuizHintContentTextView.movementMethod = LinkMovementMethod.getInstance() stepQuizHintContentTextView.setTextIsSelectable(true) - stepQuizSeeNextHintButton.root.setText(org.hyperskill.app.R.string.step_quiz_hints_see_next_hint) + stepQuizSeeNextHintButton.root.setText(R.string.step_quiz_hints_see_next_hint) stepQuizSeeNextHintButton.root.setOnClickListener { onNewMessage(StepQuizHintsFeature.Message.LoadHintButtonClicked) } @@ -58,40 +63,67 @@ class StepQuizHintsDelegate( addState(binding.stepQuizHintsRetryButton) } + fun onAction(action: StepQuizHintsFeature.Action.ViewAction) { + when (action) { + StepQuizHintsFeature.Action.ViewAction.ShowNetworkError -> + binding.root.snackbar(messageRes = R.string.connection_error) + } + } + fun render(context: Context, state: StepQuizHintsFeature.ViewState) { viewStateDelegate.switchState(state) - if (state is StepQuizHintsFeature.ViewState.Content.HintCard) { - with(binding.stepQuizHintCard) { - stepQuizHintNameTextView.setTextIfChanged(state.authorName) - stepQuizHintAvatarImageView.load(state.authorAvatar, imageLoader) { - transformations(CircleCropTransformation()) - } - if (stepQuizHintContentTextView.originalText != state.hintText) { - stepQuizHintContentTextView.originalText = state.hintText - } - stepQuizHintBeforeRateGroup.isVisible = - state.hintState == StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT - stepQuizSeeNextHintButton.root.isVisible = - state.hintState == StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT - stepQuizHintDescriptionTextView.isVisible = - state.hintState != StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT - if (state.hintState != StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT) { - @Suppress("KotlinConstantConditions") - stepQuizHintDescriptionTextView.setTextIfChanged( - when (state.hintState) { - StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT -> - org.hyperskill.app.R.string.step_quiz_hints_helpful_question_text - StepQuizHintsFeature.ViewState.HintState.LAST_HINT -> - org.hyperskill.app.R.string.step_quiz_hints_last_hint_text - StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT -> - error("Can't evaluate text for state = $state") - }.let(context::getString) + binding.root.updateLayoutParams { + updateMargins( + top = if (state is StepQuizHintsFeature.ViewState.Idle) { + 0 + } else { + context.resources.getDimensionPixelOffset( + org.hyperskill.app.android.R.dimen.step_quiz_hints_top_margin ) } - if (state.hintState == StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT) { - stepQuizHintReportTextView.setOnClickListener { - handleHintReportClick(context, onNewMessage) - } + ) + } + if (state is StepQuizHintsFeature.ViewState.Content.HintCard) { + renderCardState(state, context) + } + } + + private fun renderCardState( + state: StepQuizHintsFeature.ViewState.Content.HintCard, + context: Context + ) { + with(binding.stepQuizHintCard) { + stepQuizHintNameTextView.setTextIfChanged(state.authorName) + stepQuizHintAvatarImageView.load(state.authorAvatar, imageLoader) { + transformations(CircleCropTransformation()) + } + if (stepQuizHintContentTextView.originalText != state.hintText) { + stepQuizHintContentTextView.originalText = state.hintText + } + stepQuizHintBeforeRateGroup.isVisible = + state.hintState == StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT + stepQuizSeeNextHintButton.root.isVisible = + state.hintState == StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT + stepQuizHintDescriptionTextView.isVisible = + state.hintState != StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT + if (state.hintState != StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT) { + @Suppress("KotlinConstantConditions") + stepQuizHintDescriptionTextView.setTextIfChanged( + when (state.hintState) { + StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT -> + R.string.step_quiz_hints_helpful_question_text + + StepQuizHintsFeature.ViewState.HintState.LAST_HINT -> + R.string.step_quiz_hints_last_hint_text + + StepQuizHintsFeature.ViewState.HintState.SEE_NEXT_HINT -> + error("Can't evaluate text for state = $state") + }.let(context::getString) + ) + } + if (state.hintState == StepQuizHintsFeature.ViewState.HintState.REACT_TO_HINT) { + stepQuizHintReportTextView.setOnClickListener { + handleHintReportClick(context, onNewMessage) } } } @@ -111,14 +143,14 @@ class StepQuizHintsDelegate( onNewMessage: (StepQuizHintsFeature.Message) -> Unit ) = MaterialAlertDialogBuilder(context) - .setTitle(org.hyperskill.app.R.string.step_quiz_hints_report_alert_title) - .setMessage(org.hyperskill.app.R.string.step_quiz_hints_report_alert_text) - .setPositiveButton(org.hyperskill.app.R.string.yes) { dialog, _ -> + .setTitle(R.string.step_quiz_hints_report_alert_title) + .setMessage(R.string.step_quiz_hints_report_alert_text) + .setPositiveButton(R.string.yes) { dialog, _ -> dialog.dismiss() onNewMessage(StepQuizHintsFeature.Message.ReportHintNoticeHiddenEventMessage(true)) onNewMessage(StepQuizHintsFeature.Message.ReportHint) } - .setNegativeButton(org.hyperskill.app.R.string.no) { dialog, _ -> + .setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() onNewMessage( StepQuizHintsFeature.Message.ReportHintNoticeHiddenEventMessage(false) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/fragment/StepQuizHintsFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/fragment/StepQuizHintsFragment.kt deleted file mode 100644 index 696b9c9f80..0000000000 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_hints/fragment/StepQuizHintsFragment.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.hyperskill.app.android.step_quiz_hints.fragment - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import by.kirich1409.viewbindingdelegate.viewBinding -import coil.ImageLoader -import com.chrynan.parcelable.core.getParcelable -import com.chrynan.parcelable.core.putParcelable -import org.hyperskill.app.android.HyperskillApp -import org.hyperskill.app.android.R -import org.hyperskill.app.android.databinding.LayoutStepQuizHintsBinding -import org.hyperskill.app.android.step_quiz_hints.delegate.StepQuizHintsDelegate -import org.hyperskill.app.core.injection.ReduxViewModelFactory -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step.domain.model.StepRoute -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsViewModel -import ru.nobird.android.view.base.ui.extension.snackbar -import ru.nobird.android.view.redux.ui.extension.reduxViewModel -import ru.nobird.app.presentation.redux.container.ReduxView - -class StepQuizHintsFragment : - Fragment(R.layout.layout_step_quiz_hints), - ReduxView { - - companion object { - private const val KEY_STEP = "hints_key_step" - private const val KEY_STEP_ROUTE = "hints_key_step_route" - - fun newInstance(stepRoute: StepRoute, step: Step): Fragment = - StepQuizHintsFragment().apply { - arguments = Bundle().apply { - putParcelable(KEY_STEP, step, serializer = Step.serializer()) - putParcelable(KEY_STEP_ROUTE, stepRoute, serializer = StepRoute.serializer()) - } - } - } - - private var step: Step? = null - - private var viewModelFactory: ReduxViewModelFactory? = null - private val stepQuizHintsViewModel: StepQuizHintsViewModel by reduxViewModel(this) { - requireNotNull(viewModelFactory) - } - - private val viewBinding: LayoutStepQuizHintsBinding by viewBinding(LayoutStepQuizHintsBinding::bind) - - private var stepQuizHintsDelegate: StepQuizHintsDelegate? = null - - private val svgImageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) { - HyperskillApp.graph().imageLoadingComponent.imageLoader - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val step = requireArguments().getParcelable(KEY_STEP, deserializer = Step.serializer()) - ?: throw IllegalStateException("Step cannot be null") - this.step = step - val stepRoute = requireArguments().getParcelable(KEY_STEP_ROUTE, deserializer = StepRoute.serializer()) - ?: throw IllegalStateException("StepRoute cannot be null") - injectDependencies(stepRoute, step) - } - - private fun injectDependencies(stepRoute: StepRoute, step: Step) { - viewModelFactory = - HyperskillApp.graph() - .buildPlatformStepQuizHintsComponent(stepRoute, step) - .reduxViewModelFactory - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - step?.let { - stepQuizHintsDelegate = StepQuizHintsDelegate( - binding = viewBinding, - imageLoader = svgImageLoader, - onNewMessage = stepQuizHintsViewModel::onNewMessage - ) - } - } - - override fun onDestroyView() { - super.onDestroyView() - stepQuizHintsDelegate = null - } - - override fun onAction(action: StepQuizHintsFeature.Action.ViewAction) { - when (action) { - StepQuizHintsFeature.Action.ViewAction.ShowNetworkError -> - view?.snackbar(messageRes = org.hyperskill.app.R.string.connection_error) - } - } - - override fun render(state: StepQuizHintsFeature.ViewState) { - stepQuizHintsDelegate?.render(requireContext(), state) - } -} \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/fragment_step_quiz.xml b/androidHyperskillApp/src/main/res/layout/fragment_step_quiz.xml index 01b832b9a3..a138312251 100644 --- a/androidHyperskillApp/src/main/res/layout/fragment_step_quiz.xml +++ b/androidHyperskillApp/src/main/res/layout/fragment_step_quiz.xml @@ -31,17 +31,16 @@ app:layout_constraintVertical_bias="0" /> - + android:layout_marginTop="@dimen/step_quiz_hints_top_margin" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml index 94602061e9..e246a72cff 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_fill_blanks_binding.xml @@ -3,18 +3,19 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_marginTop="@dimen/step_quiz_content_padding_top" android:layout_marginBottom="@dimen/step_quiz_content_padding_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/stepQuizHints" - app:layout_constraintBottom_toTopOf="@id/stepQuizStatistics"> + app:layout_constraintBottom_toTopOf="@id/stepQuizStatistics" + app:layout_constraintVertical_bias="0"> diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_parsons.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_parsons.xml index c59d41574e..6aaf19d064 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_parsons.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_parsons.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:layout_marginHorizontal="@dimen/step_quiz_content_padding_horizontal" android:layout_marginTop="@dimen/step_quiz_content_padding_top" android:layout_marginBottom="@dimen/step_quiz_content_padding_bottom" app:layout_constraintBottom_toTopOf="@id/stepQuizStatistics" diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_sorting.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_sorting.xml index 00a8f0bb3d..2c17fc99a7 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_sorting.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_sorting.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:layout_marginHorizontal="@dimen/step_quiz_content_padding_horizontal" android:layout_marginTop="@dimen/step_quiz_content_padding_top" android:layout_marginBottom="@dimen/step_quiz_content_padding_bottom" app:layout_constraintVertical_bias="0" diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_text.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_text.xml index 03aa2cf802..9f65f0354e 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_text.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_text.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:layout_marginHorizontal="@dimen/step_quiz_content_padding_horizontal" android:layout_marginTop="@dimen/step_quiz_content_padding_top" android:layout_marginBottom="@dimen/step_quiz_content_padding_bottom" app:layout_constraintBottom_toTopOf="@id/stepQuizStatistics" diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index 20cd0b0a7c..94b8797558 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -57,8 +57,10 @@ 21dp 12dp 20dp + 20dp 48dp 16dp + 20dp 1dp diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml index a1caee3e97..d0c0914425 100644 --- a/gradle/app.versions.toml +++ b/gradle/app.versions.toml @@ -2,5 +2,5 @@ minSdk = '24' targetSdk = '33' compileSdk = '33' -versionName = '1.39' -versionCode = '224' \ No newline at end of file +versionName = '1.40' +versionCode = '229' \ No newline at end of file diff --git a/iosHyperskillApp/.swiftlint.yml b/iosHyperskillApp/.swiftlint.yml index 0815f2cfa8..3097da7927 100644 --- a/iosHyperskillApp/.swiftlint.yml +++ b/iosHyperskillApp/.swiftlint.yml @@ -11,7 +11,6 @@ analyzer_rules: - unused_import only_rules: - - anyobject_protocol - attributes - block_based_kvo - class_delegate_protocol @@ -159,11 +158,9 @@ only_rules: - yoda_condition # settings -anyobject_protocol: - severity: error - attributes: severity: error + attributes_with_arguments_always_on_line_above: false block_based_kvo: severity: error diff --git a/iosHyperskillApp/Gemfile b/iosHyperskillApp/Gemfile index 9e70646223..8aef70f928 100644 --- a/iosHyperskillApp/Gemfile +++ b/iosHyperskillApp/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" ruby "3.1.0" gem "fastlane", "2.216.0" -gem "cocoapods", "1.14.0" +gem "cocoapods", "1.14.2" gem "generamba", git: "https://github.com/ivan-magda/Generamba.git", branch: "develop" eval_gemfile("fastlane/Pluginfile") \ No newline at end of file diff --git a/iosHyperskillApp/Gemfile.lock b/iosHyperskillApp/Gemfile.lock index 16acc7adbe..40b70237cd 100644 --- a/iosHyperskillApp/Gemfile.lock +++ b/iosHyperskillApp/Gemfile.lock @@ -53,10 +53,10 @@ GEM base64 (0.1.1) bigdecimal (3.1.4) claide (1.1.0) - cocoapods (1.14.0) + cocoapods (1.14.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.0) + cocoapods-core (= 1.14.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -71,7 +71,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.0) + cocoapods-core (1.14.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -313,7 +313,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - cocoapods (= 1.14.0) + cocoapods (= 1.14.2) fastlane (= 2.216.0) fastlane-plugin-firebase_app_distribution fastlane-plugin-sentry diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist index 809094d704..496b129c45 100644 --- a/iosHyperskillApp/NotificationServiceExtension/Info.plist +++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist @@ -9,9 +9,9 @@ CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleVersion - 224 + 228 CFBundleShortVersionString - 1.39 + 1.40 CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleExecutable diff --git a/iosHyperskillApp/Podfile b/iosHyperskillApp/Podfile index 8ea02c0172..41cb278328 100644 --- a/iosHyperskillApp/Podfile +++ b/iosHyperskillApp/Podfile @@ -9,23 +9,23 @@ target "iosHyperskillApp" do pod "shared", :path => "../shared" - pod "SwiftLint", "0.50.1" - pod "Sentry", "8.5.0" + pod "SwiftLint", "0.53.0" + pod "Sentry", "8.14.2" # Firebase - pod "Firebase/CoreOnly", "10.11.0" - pod "Firebase/Messaging", "10.11.0" + pod "Firebase/CoreOnly", "10.17.0" + pod "Firebase/Messaging", "10.17.0" # Analytics pod "AppsFlyerFramework", "6.12.2" # Social SDKs - pod "GoogleSignIn", "6.1.0" + pod "GoogleSignIn", "6.2.4" pod "IQKeyboardManagerSwift", "6.5.9" pod "SVProgressHUD", "2.2.5" - pod "SkeletonUI", "1.0.9" - pod "lottie-ios", "4.2.0" + pod "SkeletonUI", "1.0.11" + pod "lottie-ios", "4.3.3" pod "PanModal", :git => "https://github.com/ivan-magda/PanModal.git", :branch => "remove-presenting-appearance-transitions" pod "CombineSchedulers", :git => "https://github.com/ivan-magda/combine-schedulers.git", :branch => "main" diff --git a/iosHyperskillApp/Podfile.lock b/iosHyperskillApp/Podfile.lock index 26eda5cf34..3db79320cc 100644 --- a/iosHyperskillApp/Podfile.lock +++ b/iosHyperskillApp/Podfile.lock @@ -1,35 +1,35 @@ PODS: - - AppAuth (1.6.0): - - AppAuth/Core (= 1.6.0) - - AppAuth/ExternalUserAgent (= 1.6.0) - - AppAuth/Core (1.6.0) - - AppAuth/ExternalUserAgent (1.6.0): + - AppAuth (1.6.2): + - AppAuth/Core (= 1.6.2) + - AppAuth/ExternalUserAgent (= 1.6.2) + - AppAuth/Core (1.6.2) + - AppAuth/ExternalUserAgent (1.6.2): - AppAuth/Core - AppsFlyerFramework (6.12.2): - AppsFlyerFramework/Main (= 6.12.2) - AppsFlyerFramework/Main (6.12.2) - Atributika (4.10.1) - - CocoaLumberjack (3.8.0): - - CocoaLumberjack/Core (= 3.8.0) - - CocoaLumberjack/Core (3.8.0) + - CocoaLumberjack (3.8.2): + - CocoaLumberjack/Core (= 3.8.2) + - CocoaLumberjack/Core (3.8.2) - CombineSchedulers (0.8.0) - - Firebase/CoreOnly (10.11.0): - - FirebaseCore (= 10.11.0) - - Firebase/Messaging (10.11.0): + - Firebase/CoreOnly (10.17.0): + - FirebaseCore (= 10.17.0) + - Firebase/Messaging (10.17.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.11.0) - - FirebaseCore (10.11.0): + - FirebaseMessaging (~> 10.17.0) + - FirebaseCore (10.17.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/Logger (~> 7.8) - - FirebaseCoreInternal (10.11.0): + - FirebaseCoreInternal (10.17.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.11.0): + - FirebaseInstallations (10.17.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.11.0): + - FirebaseMessaging (10.17.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.2) @@ -39,39 +39,39 @@ PODS: - GoogleUtilities/UserDefaults (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - Gifu (3.3.1) - - GoogleDataTransport (9.2.3): + - GoogleDataTransport (9.2.5): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleSignIn (6.1.0): - - AppAuth (~> 1.4) - - GTMAppAuth (~> 1.0) - - GTMSessionFetcher/Core (~> 1.1) - - GoogleUtilities/AppDelegateSwizzler (7.11.1): + - GoogleSignIn (6.2.4): + - AppAuth (~> 1.5) + - GTMAppAuth (~> 1.3) + - GTMSessionFetcher/Core (< 3.0, >= 1.1) + - GoogleUtilities/AppDelegateSwizzler (7.11.6): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.11.1): + - GoogleUtilities/Environment (7.11.6): - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.11.1): + - GoogleUtilities/Logger (7.11.6): - GoogleUtilities/Environment - - GoogleUtilities/Network (7.11.1): + - GoogleUtilities/Network (7.11.6): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.11.1)" - - GoogleUtilities/Reachability (7.11.1): + - "GoogleUtilities/NSData+zlib (7.11.6)" + - GoogleUtilities/Reachability (7.11.6): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.11.1): + - GoogleUtilities/UserDefaults (7.11.6): - GoogleUtilities/Logger - GTMAppAuth (1.3.1): - AppAuth/Core (~> 1.6) - GTMSessionFetcher/Core (< 3.0, >= 1.5) - - GTMSessionFetcher/Core (1.7.2) + - GTMSessionFetcher/Core (2.3.0) - Highlightr (2.1.0) - IQKeyboardManagerSwift (6.5.9) - Kanna (5.2.7) - - lottie-ios (4.2.0) + - lottie-ios (4.3.3) - nanopb (2.30909.0): - nanopb/decode (= 2.30909.0) - nanopb/encode (= 2.30909.0) @@ -82,43 +82,43 @@ PODS: - Gifu (~> 3.0) - Nuke (~> 10.5) - PanModal (1.2.7) - - PromisesObjC (2.2.0) - - Sentry (8.5.0): - - Sentry/Core (= 8.5.0) - - SentryPrivate (= 8.5.0) - - Sentry/Core (8.5.0): - - SentryPrivate (= 8.5.0) - - SentryPrivate (8.5.0) + - PromisesObjC (2.3.1) + - Sentry (8.14.2): + - Sentry/Core (= 8.14.2) + - SentryPrivate (= 8.14.2) + - Sentry/Core (8.14.2): + - SentryPrivate (= 8.14.2) + - SentryPrivate (8.14.2) - shared (1.0) - - SkeletonUI (1.0.9) + - SkeletonUI (1.0.11) - SnapKit (5.6.0) - STRegex (2.1.1) - - SVGKit (3.1.0): + - SVGKit (3.1.1): - CocoaLumberjack (~> 3.0) - SVProgressHUD (2.2.5) - - SwiftLint (0.50.1) + - SwiftLint (0.53.0) DEPENDENCIES: - AppsFlyerFramework (= 6.12.2) - Atributika (= 4.10.1) - CombineSchedulers (from `https://github.com/ivan-magda/combine-schedulers.git`, branch `main`) - - Firebase/CoreOnly (= 10.11.0) - - Firebase/Messaging (= 10.11.0) - - GoogleSignIn (= 6.1.0) + - Firebase/CoreOnly (= 10.17.0) + - Firebase/Messaging (= 10.17.0) + - GoogleSignIn (= 6.2.4) - Highlightr (from `https://github.com/raspu/Highlightr.git`, branch `master`) - IQKeyboardManagerSwift (= 6.5.9) - Kanna (= 5.2.7) - - lottie-ios (= 4.2.0) + - lottie-ios (= 4.3.3) - NukeUI (from `https://github.com/kean/NukeUI.git`, tag `0.8.3`) - PanModal (from `https://github.com/ivan-magda/PanModal.git`, branch `remove-presenting-appearance-transitions`) - - Sentry (= 8.5.0) + - Sentry (= 8.14.2) - shared (from `../shared`) - - SkeletonUI (= 1.0.9) + - SkeletonUI (= 1.0.11) - SnapKit (= 5.6.0) - STRegex (= 2.1.1) - SVGKit (from `https://github.com/SVGKit/SVGKit.git`, branch `3.x`) - SVProgressHUD (= 2.2.5) - - SwiftLint (= 0.50.1) + - SwiftLint (= 0.53.0) SPEC REPOS: trunk: @@ -184,45 +184,45 @@ CHECKOUT OPTIONS: :commit: 32fc8b5868b0254a2025c9c01b24c0e4b3fe537d :git: https://github.com/ivan-magda/PanModal.git SVGKit: - :commit: 7b51bfe8f53d8a10ab5a6b94121ff71f1215bf82 + :commit: 3071439e9a20c0be7cfa7fbeb72278d1482fc740 :git: https://github.com/SVGKit/SVGKit.git SPEC CHECKSUMS: - AppAuth: 8fca6b5563a5baef2c04bee27538025e4ceb2add + AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 AppsFlyerFramework: 6eb4d89d2eb9a6632317f1055b359d9fd85fd5ff Atributika: 47e778507cfb3cd2c996278b0285221a62e97d71 - CocoaLumberjack: 78abfb691154e2a9df8ded4350d504ee19d90732 + CocoaLumberjack: f8d89a516e7710fdb2e9b8f1560b16ec6040eef0 CombineSchedulers: 80f670c732b4754eb011cd1147d9a08654b1c463 - Firebase: 31d9575c124839fb5abc0db6d39511cc1dab1b85 - FirebaseCore: 62fd4d549f5e3f3bd52b7998721c5fa0557fb355 - FirebaseCoreInternal: 9e46c82a14a3b3a25be4e1e151ce6d21536b89c0 - FirebaseInstallations: 2a2c6859354cbec0a228a863d4daf6de7c74ced4 - FirebaseMessaging: 06253e9669434df0a40718fc82a90c78090f4704 + Firebase: f4ac0b02927af9253ae094d23deecf0890da7374 + FirebaseCore: 534544dd98cabcf4bf8598d88ec683b02319a528 + FirebaseCoreInternal: 2cf9202e226e3f78d2bf6d56c472686b935bfb7f + FirebaseInstallations: 9387bf15abfc69a714f54e54f74a251264fdb79b + FirebaseMessaging: 1367b28c0c83a63072af4a711328fcc2e6899902 Gifu: 416d4e38c4c2fed012f019e0a1d3ffcb58e5b842 - GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd - GoogleSignIn: c90b5bec45e780f54c6a8e1e3c182a86e3dda69d - GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 + GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 + GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a + GoogleUtilities: 202e7a9f5128accd11160fb9c19612de1911aa19 GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd - GTMSessionFetcher: 5595ec75acf5be50814f81e9189490412bad82ba + GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 Highlightr: 683f05d5223cade533a78528a35c9f06e4caddf8 IQKeyboardManagerSwift: 6e839c575c4aa1078d58a596e41244e77abe918f Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234 - lottie-ios: 809ecf2d460ed650a6aed7aa88b2ec45fab4779c + lottie-ios: 25e7b2675dad5c3ddad369ac9baab03560c5bfdd nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 Nuke: 279f17a599fd1c83cf51de5e0e1f2db143a287b0 NukeUI: 3c7ec7b299dd99707afdc783b436f39768b4493b PanModal: 3e16ead1a907fb06f4df3f13492fd00149fa4974 - PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - Sentry: 3be3f42e40e5a552935552e115744d5810a216d9 - SentryPrivate: 8c9463280e282527f938d1a5d1d60f8e10ff279b + PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 + Sentry: e0ea366f95ebb68f26d6030d8c22d6b2e6d23dd0 + SentryPrivate: 949a21fa59872427edc73b524c3ec8456761d97f shared: 210f065b47c10083f8ccc49e665dec6d32c5b2d3 - SkeletonUI: b5c868e6917123e3d442d6cef9c71fe048175f3b + SkeletonUI: a5514a3877d39f28229c852a567660d0f7542330 SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 STRegex: d49e88d0fe58538d3175fdd989bc1243b9be2a07 - SVGKit: 3c8468aab0026048532a3b27a0c81cdd939f0649 + SVGKit: 9bc0f0982df559e65b96f2150ff9c15a61e453ac SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 - SwiftLint: 6b0cf1f4d619808dbc16e4fab064ce6fc79f090b + SwiftLint: 5ce4d6a8ff83f1b5fd5ad5dbf30965d35af65e44 -PODFILE CHECKSUM: a7ef0dcd8f3df68cfb08d30b4fc2b34b4f57abae +PODFILE CHECKSUM: 5b9e11f6c950f7555e8c01422a68067fdaf449d9 -COCOAPODS: 1.14.0 +COCOAPODS: 1.14.2 diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 4192b72e1d..100904d9b3 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -301,7 +301,6 @@ 2C96744428883E710091B6C9 /* BlockOptionsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */; }; 2C971B852AC2F5DC00868FCE /* ExpandableStepTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C971B842AC2F5DC00868FCE /* ExpandableStepTextView.swift */; }; 2C975D662A1128670068FD4E /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */; }; - 2C97B0CD298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */; }; 2C97E55A2859A2C500EA1A21 /* StepQuizNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */; }; 2C97F50A28880B6F00DE77B7 /* SkeletonRoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97F50928880B6F00DE77B7 /* SkeletonRoundedButton.swift */; }; 2C98C7A62850B93100857783 /* alt.css in Resources */ = {isa = PBXBuildFile; fileRef = 2C98C79E2850B93100857783 /* alt.css */; }; @@ -367,6 +366,7 @@ 2CB45762288EC29D007C2D77 /* StepQuizActionButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB45761288EC29D007C2D77 /* StepQuizActionButtons.swift */; }; 2CB45764288ED6D4007C2D77 /* StepQuizActionButtonCodeQuizDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB45763288ED6D4007C2D77 /* StepQuizActionButtonCodeQuizDelegate.swift */; }; 2CB64A3F2ABC47590053A998 /* NotificationsOnboardingOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB64A3E2ABC47590053A998 /* NotificationsOnboardingOutputProtocol.swift */; }; + 2CB9537E2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB9537D2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift */; }; 2CBC97C62A55435E0078E445 /* StageImplementStageCompletedModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBC97C52A55435E0078E445 /* StageImplementStageCompletedModalView.swift */; }; 2CBC97C82A5549F10078E445 /* HypercoinLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBC97C72A5549F10078E445 /* HypercoinLabel.swift */; }; 2CBC97CA2A5553330078E445 /* StageImplementStageCompletedModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBC97C92A5553330078E445 /* StageImplementStageCompletedModalViewController.swift */; }; @@ -580,8 +580,6 @@ E9F2CC5329223C0200691540 /* StyledHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F2CC5229223C0200691540 /* StyledHostingController.swift */; }; E9F2CC552922694F00691540 /* TopicsRepetitionsChartBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F2CC542922694F00691540 /* TopicsRepetitionsChartBar.swift */; }; E9F504CC2912842D00B788C7 /* StepQuizHintsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F504CB2912842D00B788C7 /* StepQuizHintsView.swift */; }; - E9F504CE2912891900B788C7 /* StepQuizHintsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F504CD2912891900B788C7 /* StepQuizHintsViewModel.swift */; }; - E9F504D029128B5300B788C7 /* StepQuizHintsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F504CF29128B5300B788C7 /* StepQuizHintsAssembly.swift */; }; E9F59B90289FE053001CEA02 /* ProfileSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F59B8F289FE053001CEA02 /* ProfileSettingsViewModel.swift */; }; E9F655CA2875914200291143 /* StreakCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F655C92875914200291143 /* StreakCardView.swift */; }; E9F655D12875B32700291143 /* ProblemOfDayCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F655D02875B32700291143 /* ProblemOfDayCardView.swift */; }; @@ -938,7 +936,6 @@ 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockOptionsExtensions.swift; sourceTree = ""; }; 2C971B842AC2F5DC00868FCE /* ExpandableStepTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableStepTextView.swift; sourceTree = ""; }; 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; - 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsFeatureViewStateKsExtensions.swift; sourceTree = ""; }; 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizNameView.swift; sourceTree = ""; }; 2C97F50928880B6F00DE77B7 /* SkeletonRoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonRoundedButton.swift; sourceTree = ""; }; 2C98C79E2850B93100857783 /* alt.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = alt.css; sourceTree = ""; }; @@ -1004,6 +1001,7 @@ 2CB45761288EC29D007C2D77 /* StepQuizActionButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizActionButtons.swift; sourceTree = ""; }; 2CB45763288ED6D4007C2D77 /* StepQuizActionButtonCodeQuizDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizActionButtonCodeQuizDelegate.swift; sourceTree = ""; }; 2CB64A3E2ABC47590053A998 /* NotificationsOnboardingOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsOnboardingOutputProtocol.swift; sourceTree = ""; }; + 2CB9537D2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsFeatureStateKsExtensions.swift; sourceTree = ""; }; 2CBC97C52A55435E0078E445 /* StageImplementStageCompletedModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageImplementStageCompletedModalView.swift; sourceTree = ""; }; 2CBC97C72A5549F10078E445 /* HypercoinLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HypercoinLabel.swift; sourceTree = ""; }; 2CBC97C92A5553330078E445 /* StageImplementStageCompletedModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageImplementStageCompletedModalViewController.swift; sourceTree = ""; }; @@ -1222,8 +1220,6 @@ E9F2CC5229223C0200691540 /* StyledHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledHostingController.swift; sourceTree = ""; }; E9F2CC542922694F00691540 /* TopicsRepetitionsChartBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsRepetitionsChartBar.swift; sourceTree = ""; }; E9F504CB2912842D00B788C7 /* StepQuizHintsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsView.swift; sourceTree = ""; }; - E9F504CD2912891900B788C7 /* StepQuizHintsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsViewModel.swift; sourceTree = ""; }; - E9F504CF29128B5300B788C7 /* StepQuizHintsAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsAssembly.swift; sourceTree = ""; }; E9F59B8F289FE053001CEA02 /* ProfileSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsViewModel.swift; sourceTree = ""; }; E9F655C92875914200291143 /* StreakCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreakCardView.swift; sourceTree = ""; }; E9F655D02875B32700291143 /* ProblemOfDayCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemOfDayCardView.swift; sourceTree = ""; }; @@ -1500,7 +1496,7 @@ 2C306A0D29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift */, 2CFD7C67292542FB00902748 /* StepFeatureStateKsExtensions.swift */, 2CFD7C692925447600902748 /* StepQuizFeatureStateKsExtensions.swift */, - 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */, + 2CB9537D2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift */, E9AD65A9292DC0BE00E574F0 /* TopicsRepetitionsFeatureStateKsExtensions.swift */, 2C54E4212A1F6672003406B9 /* TrackSelectionDetailsFeatureViewStateKsExtensions.swift */, 2C0EB9532A151C2B006DC84B /* TrackSelectionListFeatureViewStateKsExtensions.swift */, @@ -3595,17 +3591,6 @@ path = Views; sourceTree = ""; }; - E9A3434E29113F8A00E0C0A4 /* Views */ = { - isa = PBXGroup; - children = ( - E9F27D772906447A007F16D7 /* StepQuizHintCardView.swift */, - E9A3434F29113FA400E0C0A4 /* StepQuizHintReactionButtonView.swift */, - E9F504CB2912842D00B788C7 /* StepQuizHintsView.swift */, - E9B2CF5E2822979400B2DC6F /* StepQuizShowHintButton.swift */, - ); - path = Views; - sourceTree = ""; - }; E9A5B98A2924FACD00EF0F39 /* TopicsRepetitionsCard */ = { isa = PBXGroup; children = ( @@ -3747,9 +3732,10 @@ E9F27D7629064456007F16D7 /* StepQuizHints */ = { isa = PBXGroup; children = ( - E9F504CF29128B5300B788C7 /* StepQuizHintsAssembly.swift */, - E9F504CD2912891900B788C7 /* StepQuizHintsViewModel.swift */, - E9A3434E29113F8A00E0C0A4 /* Views */, + E9F27D772906447A007F16D7 /* StepQuizHintCardView.swift */, + E9A3434F29113FA400E0C0A4 /* StepQuizHintReactionButtonView.swift */, + E9F504CB2912842D00B788C7 /* StepQuizHintsView.swift */, + E9B2CF5E2822979400B2DC6F /* StepQuizShowHintButton.swift */, ); path = StepQuizHints; sourceTree = ""; @@ -4231,7 +4217,6 @@ 2C078CE92AE29D0600D97E24 /* StepQuizFillBlanksViewDataMapperCache.swift in Sources */, E9A6250F28ABAE83009423EE /* OnboardingViewModel.swift in Sources */, 2C0F3CFC2A80A47600947C35 /* BadgeDetailsModalView.swift in Sources */, - E9F504D029128B5300B788C7 /* StepQuizHintsAssembly.swift in Sources */, E97BEA1E2977D26F00348EEC /* TopicCompletedModalViewController.swift in Sources */, 2CEEE03128916A3100282849 /* ProblemOfDayAssembly.swift in Sources */, 2C7CB66F2ADFB96F006F78DA /* StepQuizFillBlanksView.swift in Sources */, @@ -4281,7 +4266,6 @@ 2C1F5879280D2D1300372A37 /* WebOAuthURLParser.swift in Sources */, E99B21892887E9DA006A6154 /* StepQuizTableSkeletonView.swift in Sources */, E9F2CC552922694F00691540 /* TopicsRepetitionsChartBar.swift in Sources */, - 2C97B0CD298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift in Sources */, E94BB04A2A9DFAD700736B7C /* StepQuizParsonsViewModel.swift in Sources */, 2C5B2A1D286595960097B270 /* CodeCompletionTableViewCell.swift in Sources */, 2C27C77E28773042006A641A /* NukeManager.swift in Sources */, @@ -4357,6 +4341,7 @@ 2CE31F4D27F1E0C8008EEE66 /* AppAssembly.swift in Sources */, 2CCF3B5A280050890075D12C /* DeviceInfo.swift in Sources */, E9D90905289814AA00D0EE91 /* NotificationsRegistrationService.swift in Sources */, + 2CB9537E2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift in Sources */, 2C963BCA2812D3550036DD53 /* ProfileSettingsView.swift in Sources */, 2C772E7D28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift in Sources */, 2C963BCC2812D9330036DD53 /* ProfileSettingsAssembly.swift in Sources */, @@ -4540,7 +4525,6 @@ 2C20FBAC284F1964006D879E /* HTMLExtractor.swift in Sources */, 2C97E55A2859A2C500EA1A21 /* StepQuizNameView.swift in Sources */, 2C2ECCA7288C0BF7008DDCBA /* View+ConditionalViewModifier.swift in Sources */, - E9F504CE2912891900B788C7 /* StepQuizHintsViewModel.swift in Sources */, E9B3A18C2828093800FE248B /* StepQuizActionButton.swift in Sources */, 2C93C2D2292E574F004D1861 /* HyperskillSentryLevel+SentryLevel.swift in Sources */, 2C5CA2442A202F1400DBF2F9 /* ProjectSelectionDetailsSkeletonView.swift in Sources */, @@ -4669,7 +4653,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; @@ -4690,7 +4674,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_TEAM = 3DWS674B2M; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist; @@ -4711,7 +4695,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; @@ -4732,7 +4716,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = iosHyperskillAppTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -4753,7 +4737,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; @@ -4781,7 +4765,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_TEAM = 3DWS674B2M; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -4926,7 +4910,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; @@ -4962,7 +4946,7 @@ CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 224; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\""; DEVELOPMENT_TEAM = 3DWS674B2M; ENABLE_PREVIEWS = YES; diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist index 2da647527e..51a2fb9914 100644 --- a/iosHyperskillApp/iosHyperskillApp/Info.plist +++ b/iosHyperskillApp/iosHyperskillApp/Info.plist @@ -23,7 +23,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.39 + 1.40 CFBundleURLTypes @@ -36,7 +36,7 @@ CFBundleVersion - 224 + 228 FirebaseAppDelegateProxyEnabled FirebaseMessagingAutoInitEnabled diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureStateKsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureStateKsExtensions.swift new file mode 100644 index 0000000000..d5a18f6b02 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureStateKsExtensions.swift @@ -0,0 +1,41 @@ +import Foundation +import shared + +extension StepQuizHintsFeatureStateKs: Equatable { + public static func == (lhs: StepQuizHintsFeatureStateKs, rhs: StepQuizHintsFeatureStateKs) -> Bool { + switch (lhs, rhs) { + case (.idle, .idle): + return true + case (.loading, .loading): + return true + case (.networkError, .networkError): + return true + case (.content(let lhsData), .content(let rhsData)): + return lhsData.isEqual(rhsData) + case (.content, .idle): + return false + case (.content, .loading): + return false + case (.content, .networkError): + return false + case (.networkError, .content): + return false + case (.networkError, .idle): + return false + case (.networkError, .loading): + return false + case (.loading, .content): + return false + case (.loading, .idle): + return false + case (.loading, .networkError): + return false + case (.idle, .content): + return false + case (.idle, .loading): + return false + case (.idle, .networkError): + return false + } + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureViewStateKsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureViewStateKsExtensions.swift deleted file mode 100644 index cd29d253f4..0000000000 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizHintsFeatureViewStateKsExtensions.swift +++ /dev/null @@ -1,77 +0,0 @@ -import Foundation -import shared - -extension StepQuizHintsFeatureViewStateKs: Equatable { - public static func == (lhs: StepQuizHintsFeatureViewStateKs, rhs: StepQuizHintsFeatureViewStateKs) -> Bool { - switch (lhs, rhs) { - case (.idle, .idle): - return true - case (.initialLoading, .initialLoading): - return true - case (.hintLoading, .hintLoading): - return true - case (.error, .error): - return true - case (.content(let lhsData), .content(let rhsData)): - return StepQuizHintsFeatureViewStateContentKs(lhsData) == StepQuizHintsFeatureViewStateContentKs(rhsData) - case (.content, .error): - return false - case (.content, .hintLoading): - return false - case (.content, .idle): - return false - case (.content, .initialLoading): - return false - case (.error, .content): - return false - case (.error, .hintLoading): - return false - case (.error, .idle): - return false - case (.error, .initialLoading): - return false - case (.hintLoading, .content): - return false - case (.hintLoading, .error): - return false - case (.hintLoading, .idle): - return false - case (.hintLoading, .initialLoading): - return false - case (.initialLoading, .content): - return false - case (.initialLoading, .error): - return false - case (.initialLoading, .hintLoading): - return false - case (.initialLoading, .idle): - return false - case (.idle, .content): - return false - case (.idle, .error): - return false - case (.idle, .hintLoading): - return false - case (.idle, .initialLoading): - return false - } - } -} - -extension StepQuizHintsFeatureViewStateContentKs: Equatable { - public static func == ( - lhs: StepQuizHintsFeatureViewStateContentKs, - rhs: StepQuizHintsFeatureViewStateContentKs - ) -> Bool { - switch (lhs, rhs) { - case (.seeHintButton, .seeHintButton): - return true - case (.hintCard(let lhsData), .hintCard(let rhsData)): - return lhsData.isEqual(rhsData) - case (.hintCard, .seeHintButton): - return false - case (.seeHintButton, .hintCard): - return false - } - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift index c2bf5db9e2..5de090e5ee 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift @@ -1,6 +1,6 @@ import Foundation -@available(*, deprecated, message: "Use Xcode 15 image resources instead") +//@available(*, deprecated, message: "Use Xcode 15 image resources instead") enum Images { // MARK: - Common - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/FirstProblemOnboarding/Views/FirstProblemOnboardingContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/FirstProblemOnboarding/Views/FirstProblemOnboardingContentView.swift index 3dce11d89d..06f3285fc8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/FirstProblemOnboarding/Views/FirstProblemOnboardingContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/FirstProblemOnboarding/Views/FirstProblemOnboardingContentView.swift @@ -89,8 +89,7 @@ struct FirstProblemOnboardingContentView: View { .frame(maxHeight: appearance.illustrationMaxHeight) } - @MainActor - private var actionButton: some View { + @MainActor private var actionButton: some View { Button( buttonText, action: { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift index b53e167cb9..4b51e29dc4 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Home/HomeAssembly.swift @@ -5,10 +5,6 @@ final class HomeAssembly: UIKitAssembly { func makeModule() -> UIViewController { let homeComponent = AppGraphBridge.sharedAppGraph.buildHomeComponent() - let problemsLimitComponent = AppGraphBridge.sharedAppGraph.buildProblemsLimitComponent( - screen: ProblemsLimitScreen.home - ) - let viewModel = HomeViewModel( feature: homeComponent.homeFeature ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationsOnboarding/Views/NotificationsOnboardingContentView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationsOnboarding/Views/NotificationsOnboardingContentView.swift index db27cebe03..61356c0809 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationsOnboarding/Views/NotificationsOnboardingContentView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationsOnboarding/Views/NotificationsOnboardingContentView.swift @@ -78,8 +78,7 @@ struct NotificationsOnboardingContentView: View { .frame(maxHeight: appearance.illustrationHeight) } - @MainActor - private var actionButtons: some View { + @MainActor private var actionButtons: some View { VStack(alignment: .center, spacing: appearance.interitemSpacing) { Button( Strings.NotificationsOnboarding.buttonPrimary, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift index 59cfcbb659..0d64f4a544 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift @@ -133,6 +133,12 @@ final class StepQuizViewModel: FeatureViewModel< onNewMessage(StepQuizFeatureMessageTheoryToolbarItemClicked()) } + // MARK: StepQuizHints + + func handleStepQuizHints(message: StepQuizHintsFeatureMessage) { + onNewMessage(StepQuizFeatureMessageStepQuizHintsMessage(message: message)) + } + // MARK: Analytic private func logClickedRetryEvent() { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewData.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewData.swift index d569c24241..2642e3cfaa 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewData.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewData.swift @@ -10,6 +10,4 @@ struct StepQuizViewData { let quizName: String? let feedbackHintText: String? - - let stepHasHints: Bool } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift index 7e9b3fcf75..427611c8f5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift @@ -26,8 +26,7 @@ final class StepQuizViewDataMapper { stepText: step.block.text, quizType: quizType, quizName: nil, - feedbackHintText: nil, - stepHasHints: false + feedbackHintText: nil ) } @@ -65,20 +64,12 @@ final class StepQuizViewDataMapper { } }() - let stepHasHints: Bool = { - guard let hintsStatistic = step.commentsStatistics.first(where: { $0.thread == CommentThread.hint }) else { - return false - } - return hintsStatistic.totalCount > 0 - }() - return StepQuizViewData( formattedStats: formattedStats, stepText: step.block.text, quizType: quizType, quizName: quizName, - feedbackHintText: feedbackHintText, - stepHasHints: stepHasHints + feedbackHintText: feedbackHintText ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index 05e7b2899c..b9cc5178d2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -68,12 +68,13 @@ struct StepQuizView: View { onExpandButtonTap: viewModel.logClickedStepTextDetailsEvent ) - if viewData.stepHasHints { - StepQuizHintsAssembly( - stepID: viewModel.step.id, - stepRoute: viewModel.stepRoute - ).makeModule() - } + StepQuizHintsView( + state: viewModel.state.stepQuizHintsState, + onNewMessage: { [weak viewModel] message in + viewModel?.handleStepQuizHints(message: message) + } + ) + .equatable() buildQuizContent( state: viewModel.state, @@ -306,6 +307,11 @@ struct StepQuizView: View { let assembly = StepAssembly(stepRoute: navigateToStepScreenViewAction.stepRoute) stackRouter.pushViewController(assembly.makeModule()) } + case .stepQuizHintsViewAction(let stepQuizHintsViewAction): + switch StepQuizHintsFeatureActionViewActionKs(stepQuizHintsViewAction.viewAction) { + case .showNetworkError: + ProgressHUD.showError(status: Strings.Common.connectionError) + } } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintCardView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintCardView.swift similarity index 84% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintCardView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintCardView.swift index 268ba35403..c8dfed7f1c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintCardView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintCardView.swift @@ -35,6 +35,29 @@ struct StepQuizHintCardView: View { } var body: some View { + content + .alert(isPresented: $isPresentingReportAlert) { + Alert( + title: Text(Strings.StepQuiz.Hints.reportAlertTitle), + message: Text(Strings.StepQuiz.Hints.reportAlertText), + primaryButton: .default( + Text(Strings.Common.no), + action: onReportAlertCanceled + ), + secondaryButton: .default( + Text(Strings.Common.yes), + action: onReportAlertConfirmed + ) + ) + } + .onChange(of: isPresentingReportAlert) { newValue in + if newValue { + onReportAlertAppeared() + } + } + } + + private var content: some View { VStack(alignment: .leading, spacing: LayoutInsets.defaultInset) { HStack { LazyAvatarView(authorAvatarSource) @@ -84,13 +107,11 @@ struct StepQuizHintCardView: View { isShowingMore = false onNextHintTapped() } + } else if hintState == .lastHint { + Text(Strings.StepQuiz.Hints.lastHint) + .font(.caption) + .foregroundColor(.secondaryText) } else { - if hintState == .lastHint { - Text(Strings.StepQuiz.Hints.lastHint) - .font(.caption) - .foregroundColor(.secondaryText) - } - HStack(spacing: LayoutInsets.smallInset) { Text(Strings.StepQuiz.Hints.helpfulQuestion) .font(.caption) @@ -115,25 +136,6 @@ struct StepQuizHintCardView: View { .padding() .background(Color.background) .addBorder(color: Color(ColorPalette.onSurfaceAlpha9)) - .alert(isPresented: $isPresentingReportAlert) { - Alert( - title: Text(Strings.StepQuiz.Hints.reportAlertTitle), - message: Text(Strings.StepQuiz.Hints.reportAlertText), - primaryButton: .default( - Text(Strings.Common.no), - action: onReportAlertCanceled - ), - secondaryButton: .default( - Text(Strings.Common.yes), - action: onReportAlertConfirmed - ) - ) - } - .onChange(of: isPresentingReportAlert) { newValue in - if newValue { - onReportAlertAppeared() - } - } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintReactionButtonView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintReactionButtonView.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintReactionButtonView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintReactionButtonView.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsAssembly.swift deleted file mode 100644 index ab357acef8..0000000000 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsAssembly.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation -import shared - -final class StepQuizHintsAssembly: Assembly { - private let stepID: Int64 - private let stepRoute: StepRoute - - init(stepID: Int64, stepRoute: StepRoute) { - self.stepID = stepID - self.stepRoute = stepRoute - } - - func makeModule() -> StepQuizHintsView { - let stepQuizHintsComponent = AppGraphBridge.sharedAppGraph.buildStepQuizHintsComponent( - stepRoute: stepRoute - ) - - let viewModel = StepQuizHintsViewModel( - stepID: self.stepID, - feature: stepQuizHintsComponent.stepQuizHintsFeature - ) - - return StepQuizHintsView(viewModel: viewModel) - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsView.swift new file mode 100644 index 0000000000..a3a0885d2e --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsView.swift @@ -0,0 +1,118 @@ +import shared +import SVProgressHUD +import SwiftUI + +extension StepQuizHintsView { + struct Appearance { + let skeletonInitialSize = CGSize(width: 146, height: 34) + let skeletonHintHeight: CGFloat = 152 + } +} + +struct StepQuizHintsView: View { + private(set) var appearance = Appearance() + + var state: StepQuizHintsFeatureState + var onNewMessage: (StepQuizHintsFeatureMessage) -> Void + + private var viewStateKs: StepQuizHintsFeatureViewStateKs { + let viewState = StepQuizHintsViewStateMapper.shared.mapState(state: state) + return StepQuizHintsFeatureViewStateKs(viewState) + } + + var body: some View { + buildBody() + } + + // MARK: Private API + + @ViewBuilder + private func buildBody() -> some View { + switch viewStateKs { + case .idle: + EmptyView() + case .initialLoading: + SkeletonRoundedView(appearance: .init(size: appearance.skeletonInitialSize)) + case .hintLoading: + SkeletonRoundedView() + .frame(height: appearance.skeletonHintHeight) + case .error: + StepQuizShowHintButton( + text: Strings.Placeholder.networkErrorButtonText, + showLightning: false, + onClick: { + onNewMessage(StepQuizHintsFeatureMessageLoadHintButtonClicked()) + } + ) + case .content(let sealedState): + buildContent(state: StepQuizHintsFeatureViewStateContentKs(sealedState)) + } + } + + @ViewBuilder + private func buildContent(state: StepQuizHintsFeatureViewStateContentKs) -> some View { + switch state { + case .seeHintButton: + StepQuizShowHintButton( + text: Strings.StepQuiz.Hints.showButton, + onClick: { + onNewMessage(StepQuizHintsFeatureMessageLoadHintButtonClicked()) + } + ) + case .hintCard(let data): + StepQuizHintCardView( + authorAvatarSource: data.authorAvatar, + authorName: data.authorName, + hintText: data.hintText, + hintState: data.hintState, + onReactionTapped: { reaction in + onNewMessage(StepQuizHintsFeatureMessageReactionButtonClicked(reaction: reaction)) + }, + onReportTapped: { + onNewMessage(StepQuizHintsFeatureMessageClickedReportEventMessage()) + }, + onReportAlertAppeared: { + onNewMessage(StepQuizHintsFeatureMessageReportHintNoticeShownEventMessage()) + }, + onReportAlertConfirmed: { + onNewMessage(StepQuizHintsFeatureMessageReportHint()) + onNewMessage(StepQuizHintsFeatureMessageReportHintNoticeHiddenEventMessage(isReported: true)) + }, + onReportAlertCanceled: { + onNewMessage(StepQuizHintsFeatureMessageReportHintNoticeHiddenEventMessage(isReported: true)) + }, + onNextHintTapped: { + onNewMessage(StepQuizHintsFeatureMessageLoadHintButtonClicked()) + } + ) + } + } +} + +extension StepQuizHintsView: Equatable { + static func == (lhs: StepQuizHintsView, rhs: StepQuizHintsView) -> Bool { + StepQuizHintsFeatureStateKs(lhs.state) == StepQuizHintsFeatureStateKs(rhs.state) + } +} + +#Preview { + StepQuizHintsView( + state: StepQuizHintsFeatureStateIdle(), + onNewMessage: { _ in } + ) +} + +#Preview { + StepQuizHintsView( + state: StepQuizHintsFeatureStateLoading(isInitialLoading: true), + onNewMessage: { _ in } + ) +} + +#Preview { + StepQuizHintsView( + state: StepQuizHintsFeatureStateLoading(isInitialLoading: false), + onNewMessage: { _ in } + ) + .padding() +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsViewModel.swift deleted file mode 100644 index 3ead9d8311..0000000000 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizHintsViewModel.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation -import shared - -final class StepQuizHintsViewModel: FeatureViewModel< - StepQuizHintsFeatureViewState, - StepQuizHintsFeatureMessage, - StepQuizHintsFeatureActionViewAction -> { - let stepID: Int64 - - var stateKs: StepQuizHintsFeatureViewStateKs { .init(state) } - - init(stepID: Int64, feature: Presentation_reduxFeature) { - self.stepID = stepID - super.init(feature: feature) - - onNewMessage(StepQuizHintsFeatureMessageInitWithStepId(stepId: stepID)) - } - - override func shouldNotifyStateDidChange( - oldState: StepQuizHintsFeatureViewState, - newState: StepQuizHintsFeatureViewState - ) -> Bool { - StepQuizHintsFeatureViewStateKs(oldState) != StepQuizHintsFeatureViewStateKs(newState) - } - - func onHintReactionButtonTap(reaction: ReactionType) { - onNewMessage(StepQuizHintsFeatureMessageReactionButtonClicked(reaction: reaction)) - } - - func onHintReportConfirmationButtonTap() { - onNewMessage(StepQuizHintsFeatureMessageReportHint()) - } - - func onLoadHintButtonTap() { - onNewMessage(StepQuizHintsFeatureMessageLoadHintButtonClicked()) - } - - // MARK: Analytic - - func logHintNoticeShownEvent() { - onNewMessage(StepQuizHintsFeatureMessageReportHintNoticeShownEventMessage()) - } - - func logClickedReportEvent() { - onNewMessage(StepQuizHintsFeatureMessageClickedReportEventMessage()) - } - - func logHintNoticeHiddenEvent(isReported: Bool) { - onNewMessage(StepQuizHintsFeatureMessageReportHintNoticeHiddenEventMessage(isReported: isReported)) - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizShowHintButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizShowHintButton.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizShowHintButton.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/StepQuizShowHintButton.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintsView.swift deleted file mode 100644 index 8be4288329..0000000000 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizHints/Views/StepQuizHintsView.swift +++ /dev/null @@ -1,90 +0,0 @@ -import shared -import SVProgressHUD -import SwiftUI - -extension StepQuizHintsView { - struct Appearance { - let skeletonInitialSize = CGSize(width: 146, height: 34) - let skeletonHintHeight: CGFloat = 152 - } -} - -struct StepQuizHintsView: View { - private(set) var appearance = Appearance() - - @StateObject var viewModel: StepQuizHintsViewModel - - var body: some View { - buildBody() - .onAppear { - viewModel.startListening() - viewModel.onViewAction = handleViewAction(_:) - } - .onDisappear { - viewModel.stopListening() - viewModel.onViewAction = nil - } - } - - // MARK: Private API - - @ViewBuilder - private func buildBody() -> some View { - switch viewModel.stateKs { - case .idle, .initialLoading: - SkeletonRoundedView() - .frame(size: appearance.skeletonInitialSize) - case .hintLoading: - SkeletonRoundedView() - .frame(height: appearance.skeletonHintHeight) - case .error: - StepQuizShowHintButton( - text: Strings.Placeholder.networkErrorButtonText, - showLightning: false, - onClick: viewModel.onLoadHintButtonTap - ) - case .content(let sealedState): - buildContent(state: StepQuizHintsFeatureViewStateContentKs(sealedState)) - } - } - - @ViewBuilder - private func buildContent(state: StepQuizHintsFeatureViewStateContentKs) -> some View { - switch state { - case .seeHintButton: - StepQuizShowHintButton( - text: Strings.StepQuiz.Hints.showButton, - onClick: viewModel.onLoadHintButtonTap - ) - case .hintCard(let data): - StepQuizHintCardView( - authorAvatarSource: data.authorAvatar, - authorName: data.authorName, - hintText: data.hintText, - hintState: data.hintState, - onReactionTapped: viewModel.onHintReactionButtonTap(reaction:), - onReportTapped: viewModel.logClickedReportEvent, - onReportAlertAppeared: viewModel.logHintNoticeShownEvent, - onReportAlertConfirmed: { - viewModel.onHintReportConfirmationButtonTap() - viewModel.logHintNoticeHiddenEvent(isReported: true) - }, - onReportAlertCanceled: { viewModel.logHintNoticeHiddenEvent(isReported: false) }, - onNextHintTapped: viewModel.onLoadHintButtonTap - ) - } - } - - private func handleViewAction(_ viewAction: StepQuizHintsFeatureActionViewAction) { - switch StepQuizHintsFeatureActionViewActionKs(viewAction) { - case .showNetworkError: - ProgressHUD.showError(status: Strings.Common.connectionError) - } - } -} - -struct StepQuizHintsView_Previews: PreviewProvider { - static var previews: some View { - StepQuizHintsAssembly(stepID: 15343, stepRoute: StepRouteLearnStep(stepId: 15343)).makeModule() - } -} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/LottieAnimationViewWrapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/LottieAnimationViewWrapper.swift index 97dbffe3ee..ddd6acae1a 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/LottieAnimationViewWrapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/Wrappers/LottieAnimationViewWrapper.swift @@ -10,13 +10,13 @@ struct LottieAnimationFileName { struct LottieAnimationViewWrapper: UIViewRepresentable { let fileName: LottieAnimationFileName + @Environment(\.colorScheme) var colorScheme + func makeUIView(context: Context) -> some UIView { let view = UIView(frame: .zero) - let fileNameForCurrentInterfaceStyle = view.isDarkInterfaceStyle ? fileName.dark : fileName.light - let animationView = LottieAnimationView() - animationView.animation = LottieAnimation.named(fileNameForCurrentInterfaceStyle) + animationView.animation = getLottieAnimationForCurrentColorScheme() animationView.contentMode = .scaleAspectFit animationView.loopMode = .autoReverse animationView.play() @@ -32,20 +32,55 @@ struct LottieAnimationViewWrapper: UIViewRepresentable { return view } - func updateUIView(_ uiView: UIViewType, context: Context) {} + func updateUIView(_ uiView: UIViewType, context: Context) { + guard let animationView = uiView.subviews.first as? LottieAnimationView else { + return assertionFailure("LottieAnimationViewWrapper: animationView is not found") + } + + if context.coordinator.previousColorScheme != colorScheme { + animationView.animation = getLottieAnimationForCurrentColorScheme() + animationView.play() + } + + context.coordinator.previousColorScheme = colorScheme + } + + func makeCoordinator() -> Coordinator { + Coordinator(initialColorScheme: colorScheme) + } + + private func getLottieAnimationForCurrentColorScheme() -> LottieAnimation? { + switch colorScheme { + case .dark: + LottieAnimation.named(fileName.dark) + default: + LottieAnimation.named(fileName.light) + } + } } -struct LottieAnimationViewWrapper_Previews: PreviewProvider { - static var previews: some View { - LottieAnimationViewWrapper( - fileName: LottieAnimations.parsonsProblemOnboarding - ) - .padding() - - LottieAnimationViewWrapper( - fileName: LottieAnimations.parsonsProblemOnboarding - ) - .padding() - .preferredColorScheme(.dark) +extension LottieAnimationViewWrapper { + class Coordinator { + var previousColorScheme: ColorScheme? + + init(initialColorScheme: ColorScheme) { + self.previousColorScheme = initialColorScheme + } } } + +#Preview("Light") { + LottieAnimationViewWrapper( + fileName: LottieAnimations.parsonsProblemOnboarding + ) + .padding() + .preferredColorScheme(.light) +} + +#Preview("Dark") { + LottieAnimationViewWrapper( + fileName: LottieAnimations.parsonsProblemOnboarding + ) + .padding() + .preferredColorScheme(.dark) +} diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist index 730b073cc1..3a1937f33e 100644 --- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist @@ -13,8 +13,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.39 + 1.40 CFBundleVersion - 224 + 228 diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist index 654ebb8877..387e34ef9e 100644 --- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist +++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist @@ -13,8 +13,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.39 + 1.40 CFBundleVersion - 224 + 228 diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt index e178e95504..df1499dc83 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraph.kt @@ -26,13 +26,10 @@ import org.hyperskill.app.project_selection.details.injection.ProjectSelectionDe import org.hyperskill.app.project_selection.list.injection.PlatformProjectSelectionListComponent import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListParams import org.hyperskill.app.stage_implementation.injection.PlatformStageImplementationComponent -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.injection.PlatformStepComponent import org.hyperskill.app.step.injection.StepComponent import org.hyperskill.app.step_quiz.injection.PlatformStepQuizComponent import org.hyperskill.app.step_quiz.injection.StepQuizComponent -import org.hyperskill.app.step_quiz_hints.injection.PlatformStepQuizHintsComponent import org.hyperskill.app.study_plan.injection.PlatformStudyPlanScreenComponent import org.hyperskill.app.topics_repetitions.injection.PlatformTopicsRepetitionComponent import org.hyperskill.app.track_selection.details.injection.PlatformTrackSelectionDetailsComponent @@ -57,8 +54,6 @@ interface CommonAndroidAppGraph : AppGraph { fun buildPlatformStepQuizComponent(stepQuizComponent: StepQuizComponent): PlatformStepQuizComponent - fun buildPlatformStepQuizHintsComponent(stepRoute: StepRoute, step: Step): PlatformStepQuizHintsComponent - fun buildPlatformProfileComponent(profileComponent: ProfileComponent): PlatformProfileComponent fun buildPlatformProfileSettingsComponent( diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt index 182293684c..e54799c8a7 100644 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt +++ b/shared/src/androidMain/kotlin/org/hyperskill/app/core/injection/CommonAndroidAppGraphImpl.kt @@ -41,16 +41,12 @@ import org.hyperskill.app.project_selection.list.injection.PlatformProjectSelect import org.hyperskill.app.project_selection.list.injection.ProjectSelectionListParams import org.hyperskill.app.stage_implementation.injection.PlatformStageImplementationComponent import org.hyperskill.app.stage_implementation.injection.PlatformStageImplementationComponentImpl -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.injection.PlatformStepComponent import org.hyperskill.app.step.injection.PlatformStepComponentImpl import org.hyperskill.app.step.injection.StepComponent import org.hyperskill.app.step_quiz.injection.PlatformStepQuizComponent import org.hyperskill.app.step_quiz.injection.PlatformStepQuizComponentImpl import org.hyperskill.app.step_quiz.injection.StepQuizComponent -import org.hyperskill.app.step_quiz_hints.injection.PlatformStepQuizHintsComponent -import org.hyperskill.app.step_quiz_hints.injection.PlatformStepQuizHintsComponentImpl import org.hyperskill.app.study_plan.injection.PlatformStudyPlanScreenComponent import org.hyperskill.app.study_plan.injection.PlatformStudyPlanScreenComponentImpl import org.hyperskill.app.topics_repetitions.injection.PlatformTopicsRepetitionComponent @@ -95,12 +91,6 @@ abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph() override fun buildPlatformStepQuizComponent(stepQuizComponent: StepQuizComponent): PlatformStepQuizComponent = PlatformStepQuizComponentImpl(stepQuizComponent) - /** - * Step quiz hints component - */ - override fun buildPlatformStepQuizHintsComponent(stepRoute: StepRoute, step: Step): PlatformStepQuizHintsComponent = - PlatformStepQuizHintsComponentImpl(this, stepRoute, step) - /** * Stage implement component */ diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponent.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponent.kt deleted file mode 100644 index 9f46c7e5aa..0000000000 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.hyperskill.app.step_quiz_hints.injection - -import org.hyperskill.app.core.injection.ReduxViewModelFactory - -interface PlatformStepQuizHintsComponent { - val reduxViewModelFactory: ReduxViewModelFactory -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponentImpl.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponentImpl.kt deleted file mode 100644 index 7c8e24341e..0000000000 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/PlatformStepQuizHintsComponentImpl.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.hyperskill.app.step_quiz_hints.injection - -import org.hyperskill.app.core.injection.AppGraph -import org.hyperskill.app.core.injection.ReduxViewModelFactory -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step.domain.model.StepRoute -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsViewModel -import ru.nobird.app.presentation.redux.container.wrapWithViewContainer - -class PlatformStepQuizHintsComponentImpl( - appGraph: AppGraph, - stepRoute: StepRoute, - private val step: Step -) : PlatformStepQuizHintsComponent { - - private val stepQuizHintComponent = StepQuizHintsComponentImpl(appGraph, stepRoute) - - override val reduxViewModelFactory: ReduxViewModelFactory - get() = ReduxViewModelFactory( - viewModelMap = mapOf( - StepQuizHintsViewModel::class.java to - { - StepQuizHintsViewModel( - reduxViewContainer = stepQuizHintComponent.stepQuizHintsFeature.wrapWithViewContainer(), - step = step - ) - } - ) - ) -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsViewModel.kt b/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsViewModel.kt deleted file mode 100644 index b7d1e7ea9f..0000000000 --- a/shared/src/androidMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsViewModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.hyperskill.app.step_quiz_hints.presentation - -import org.hyperskill.app.step.domain.model.Step -import ru.nobird.android.view.redux.viewmodel.ReduxViewModel -import ru.nobird.app.presentation.redux.container.ReduxViewContainer - -class StepQuizHintsViewModel( - reduxViewContainer: ReduxViewContainer< - StepQuizHintsFeature.ViewState, StepQuizHintsFeature.Message, StepQuizHintsFeature.Action.ViewAction>, - step: Step -) : ReduxViewModel< - StepQuizHintsFeature.ViewState, - StepQuizHintsFeature.Message, - StepQuizHintsFeature.Action.ViewAction>(reduxViewContainer) { - init { - if (StepQuizHintsFeature.isHintsFeatureAvailable(step)) { - onNewMessage( - StepQuizHintsFeature.Message.InitWithStepId(step.id) - ) - } - } -} \ No newline at end of file diff --git a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt index bba0b616e8..3a9b96becf 100644 --- a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt +++ b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt @@ -6,6 +6,8 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizReducer +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer import org.hyperskill.step.domain.model.stub import org.hyperskill.step_quiz.domain.model.stub import org.junit.Test @@ -38,27 +40,31 @@ class AndroidStepQuizTest { "Unknown step route class: $concreteStepRouteClass. Please add it to the test." ) } - ) + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle ) + val stepRoute = when (concreteStepRouteClass) { + StepRoute.Learn.Step::class -> StepRoute.Learn.Step(step.id) + StepRoute.Learn.TheoryOpenedFromPractice::class -> + StepRoute.Learn.TheoryOpenedFromPractice(step.id) + StepRoute.LearnDaily::class -> StepRoute.LearnDaily(step.id) + StepRoute.Repeat.Practice::class -> StepRoute.Repeat.Practice(step.id) + StepRoute.Repeat.Theory::class -> StepRoute.Repeat.Theory(step.id) + StepRoute.StageImplement::class -> StepRoute.StageImplement(step.id, 1, 1) + else -> throw IllegalStateException( + "Unknown step route class: $concreteStepRouteClass. Please add it to the test." + ) + } val reducer = StepQuizReducer( - when (concreteStepRouteClass) { - StepRoute.Learn.Step::class -> StepRoute.Learn.Step(step.id) - StepRoute.Learn.TheoryOpenedFromPractice::class -> - StepRoute.Learn.TheoryOpenedFromPractice(step.id) - StepRoute.LearnDaily::class -> StepRoute.LearnDaily(step.id) - StepRoute.Repeat.Practice::class -> StepRoute.Repeat.Practice(step.id) - StepRoute.Repeat.Theory::class -> StepRoute.Repeat.Theory(step.id) - StepRoute.StageImplement::class -> StepRoute.StageImplement(step.id, 1, 1) - else -> throw IllegalStateException( - "Unknown step route class: $concreteStepRouteClass. Please add it to the test." - ) - } + stepRoute = stepRoute, + stepQuizHintsReducer = StepQuizHintsReducer(stepRoute) ) val (state, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading + stepQuizState = StepQuizFeature.StepQuizState.Loading, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), StepQuizFeature.Message.FetchAttemptSuccess( step, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt index 582e247c24..8b4fd4948f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeatureKeys.kt @@ -5,4 +5,5 @@ object FeatureKeys { const val RECOMMENDATIONS_KOTLIN_PROJECTS = "recommendations.kotlin_projects" const val RECOMMENDATIONS_PYTHON_PROJECTS = "recommendations.python_projects" const val FREEMIUM_INCREASE_LIMITS_FOR_FIRST_STEP_COMPLETION = "freemium.increase_limits_for_first_step_completion" + const val LEARNING_PATH_DIVIDED_TRACK_TOPICS = "learning_path.divided_track_topics" } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt index 2402d451e4..3fb2e70272 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/profile/domain/model/FeaturesMap.kt @@ -12,4 +12,7 @@ val FeaturesMap.isRecommendationsPythonProjectsFeatureEnabled: Boolean get() = get(FeatureKeys.RECOMMENDATIONS_PYTHON_PROJECTS) ?: false val FeaturesMap.isFreemiumIncreaseLimitsForFirstStepCompletionEnabled: Boolean - get() = get(FeatureKeys.FREEMIUM_INCREASE_LIMITS_FOR_FIRST_STEP_COMPLETION) ?: false \ No newline at end of file + get() = get(FeatureKeys.FREEMIUM_INCREASE_LIMITS_FOR_FIRST_STEP_COMPLETION) ?: false + +val FeaturesMap.isLearningPathDividedTrackTopicsEnabled: Boolean + get() = get(FeatureKeys.LEARNING_PATH_DIVIDED_TRACK_TOPICS) ?: false \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt index d4c542c888..88016584ce 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt @@ -230,6 +230,12 @@ object HyperskillSentryTransactionBuilder { operation = HyperskillSentryTransactionOperation.API_LOAD ) + fun buildStudyPlanWidgetFetchProfile(): HyperskillSentryTransaction = + HyperskillSentryTransaction( + name = "study-plan-widget-feature-fetch-profile", + operation = HyperskillSentryTransactionOperation.API_LOAD + ) + /** * ProjectSelectionListFeature */ diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt index e38bfd3545..eaadafc538 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt @@ -11,6 +11,7 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.remote.AttemptRemoteDataSourceImpl import org.hyperskill.app.step_quiz.view.mapper.StepQuizStatsTextMapper import org.hyperskill.app.step_quiz.view.mapper.StepQuizTitleMapper +import org.hyperskill.app.step_quiz_hints.injection.StepQuizHintsComponent import ru.nobird.app.presentation.redux.feature.Feature class StepQuizComponentImpl( @@ -41,6 +42,9 @@ class StepQuizComponentImpl( appGraph.submissionDataComponent.submissionRepository ) + private val stepQuizHintsComponent: StepQuizHintsComponent = + appGraph.buildStepQuizHintsComponent(stepRoute) + override val stepQuizFeature: Feature get() = StepQuizFeatureBuilder.build( stepRoute, @@ -51,6 +55,8 @@ class StepQuizComponentImpl( appGraph.analyticComponent.analyticInteractor, appGraph.sentryComponent.sentryInteractor, appGraph.buildOnboardingComponent().onboardingInteractor, + stepQuizHintsComponent.stepQuizHintsReducer, + stepQuizHintsComponent.stepQuizHintsActionDispatcher, appGraph.commonComponent.resourceProvider, appGraph.loggerComponent.logger, appGraph.commonComponent.buildKonfig.buildVariant diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt index 597a1cabd4..aae15fe0b8 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt @@ -16,6 +16,11 @@ import org.hyperskill.app.step_quiz.domain.validation.StepQuizReplyValidator import org.hyperskill.app.step_quiz.presentation.StepQuizActionDispatcher import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizReducer +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer +import ru.nobird.app.core.model.safeCast +import ru.nobird.app.presentation.redux.dispatcher.transform import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature @@ -32,13 +37,16 @@ object StepQuizFeatureBuilder { analyticInteractor: AnalyticInteractor, sentryInteractor: SentryInteractor, onboardingInteractor: OnboardingInteractor, + stepQuizHintsReducer: StepQuizHintsReducer, + stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher, resourceProvider: ResourceProvider, logger: Logger, buildVariant: BuildVariant ): Feature { - val stepQuizReducer = - StepQuizReducer(stepRoute) - .wrapWithLogger(buildVariant, logger, LOG_TAG) + val stepQuizReducer = StepQuizReducer( + stepRoute = stepRoute, + stepQuizHintsReducer = stepQuizHintsReducer + ).wrapWithLogger(buildVariant, logger, LOG_TAG) val stepQuizActionDispatcher = StepQuizActionDispatcher( ActionDispatcherOptions(), stepQuizInteractor, @@ -53,9 +61,17 @@ object StepQuizFeatureBuilder { return ReduxFeature( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Idle + stepQuizState = StepQuizFeature.StepQuizState.Idle, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), stepQuizReducer - ).wrapWithActionDispatcher(stepQuizActionDispatcher) + ) + .wrapWithActionDispatcher(stepQuizActionDispatcher) + .wrapWithActionDispatcher( + stepQuizHintsActionDispatcher.transform( + transformAction = { it.safeCast()?.action }, + transformMessage = StepQuizFeature.Message::StepQuizHintsMessage + ) + ) } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt index 6659d39b02..3efa28da65 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt @@ -8,10 +8,12 @@ import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature interface StepQuizFeature { data class State( - val stepQuizState: StepQuizState + val stepQuizState: StepQuizState, + val stepQuizHintsState: StepQuizHintsFeature.State ) sealed interface StepQuizState { @@ -116,6 +118,11 @@ interface StepQuizFeature { object ProblemsLimitReachedModalHiddenEventMessage : Message object ParsonsProblemOnboardingModalHiddenEventMessage : Message + + /** + * Message Wrappers + */ + data class StepQuizHintsMessage(val message: StepQuizHintsFeature.Message) : Message } sealed interface Action { @@ -144,6 +151,11 @@ interface StepQuizFeature { */ data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : Action + /** + * Action Wrappers + */ + data class StepQuizHintsAction(val action: StepQuizHintsFeature.Action) : Action + sealed interface ViewAction : Action { object ShowNetworkError : ViewAction // error @@ -153,6 +165,10 @@ interface StepQuizFeature { object ShowParsonsProblemOnboardingModal : ViewAction + data class StepQuizHintsViewAction( + val viewAction: StepQuizHintsFeature.Action.ViewAction + ) : ViewAction + sealed interface NavigateTo : ViewAction { object Home : NavigateTo diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt index 50a18b3f91..80f923a40f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt @@ -26,12 +26,15 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Action import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.State import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.StepQuizState +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StepQuizReducerResult = Pair> class StepQuizReducer( - private val stepRoute: StepRoute + private val stepRoute: StepRoute, + private val stepQuizHintsReducer: StepQuizHintsReducer ) : StateReducer { override fun reduce(state: State, message: Message): StepQuizReducerResult = when (message) { @@ -292,6 +295,12 @@ class StepQuizReducer( ParsonsProblemOnboardingModalHiddenHyperskillAnalyticEvent(stepRoute.analyticRoute) ) ) + // Wrapper Messages + is Message.StepQuizHintsMessage -> { + val (stepQuizHintsState, stepQuizHintsActions) = + reduceStepQuizHintsMessage(state.stepQuizHintsState, message.message) + state.copy(stepQuizHintsState = stepQuizHintsState) to stepQuizHintsActions + } } ?: (state to emptySet()) private fun handleFetchAttemptSuccess(state: State, message: Message.FetchAttemptSuccess): StepQuizReducerResult = @@ -341,7 +350,20 @@ class StepQuizReducer( state.stepQuizState to emptySet() } - return state.copy(stepQuizState = stepQuizState) to stepQuizActions + val (stepQuizHintsState, stepQuizHintsActions) = + if (StepQuizHintsFeature.isHintsFeatureAvailable(step = message.step)) { + reduceStepQuizHintsMessage( + state.stepQuizHintsState, + StepQuizHintsFeature.Message.InitWithStepId(message.step.id) + ) + } else { + StepQuizHintsFeature.State.Idle to emptySet() + } + + return state.copy( + stepQuizState = stepQuizState, + stepQuizHintsState = stepQuizHintsState + ) to stepQuizActions + stepQuizHintsActions } private fun handleTheoryToolbarItemClicked(state: State): StepQuizReducerResult = @@ -391,4 +413,23 @@ class StepQuizReducer( time = Clock.System.now().toString() ) } + + private fun reduceStepQuizHintsMessage( + state: StepQuizHintsFeature.State, + message: StepQuizHintsFeature.Message + ): Pair> { + val (stepQuizHintsState, stepQuizHintsActions) = stepQuizHintsReducer.reduce(state, message) + + val actions = stepQuizHintsActions + .map { + if (it is StepQuizHintsFeature.Action.ViewAction) { + Action.ViewAction.StepQuizHintsViewAction(it) + } else { + Action.StepQuizHintsAction(it) + } + } + .toSet() + + return stepQuizHintsState to actions + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponent.kt index 91ded9d876..26752f5655 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponent.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponent.kt @@ -1,9 +1,9 @@ package org.hyperskill.app.step_quiz_hints.injection -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -import ru.nobird.app.presentation.redux.feature.Feature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer interface StepQuizHintsComponent { - val stepQuizHintsFeature: Feature< - StepQuizHintsFeature.ViewState, StepQuizHintsFeature.Message, StepQuizHintsFeature.Action> + val stepQuizHintsReducer: StepQuizHintsReducer + val stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt index 1fbb1b06c4..6e1b43462f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsComponentImpl.kt @@ -1,10 +1,11 @@ package org.hyperskill.app.step_quiz_hints.injection import org.hyperskill.app.core.injection.AppGraph +import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz_hints.domain.interactor.StepQuizHintsInteractor -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -import ru.nobird.app.presentation.redux.feature.Feature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer class StepQuizHintsComponentImpl( private val appGraph: AppGraph, @@ -12,24 +13,24 @@ class StepQuizHintsComponentImpl( ) : StepQuizHintsComponent { private val stepQuizHintsInteractor: StepQuizHintsInteractor = StepQuizHintsInteractor( - appGraph.buildDiscussionsDataComponent().discussionsRepository, - appGraph.buildUserStorageComponent().userStorageRepository, - appGraph.buildCommentsDataComponent().commentsRepository + discussionsRepository = appGraph.buildDiscussionsDataComponent().discussionsRepository, + userStorageRepository = appGraph.buildUserStorageComponent().userStorageRepository, + commentsRepository = appGraph.buildCommentsDataComponent().commentsRepository ) - override val stepQuizHintsFeature: Feature< - StepQuizHintsFeature.ViewState, StepQuizHintsFeature.Message, StepQuizHintsFeature.Action> - get() = StepQuizHintsFeatureBuilder.build( - stepRoute, - stepQuizHintsInteractor, - appGraph.buildLikesDataComponent().likesInteractor, - appGraph.buildCommentsDataComponent().commentsInteractor, - appGraph.buildReactionsDataComponent().reactionsInteractor, - appGraph.buildUserStorageComponent().userStorageInteractor, - appGraph.buildFreemiumDataComponent().freemiumInteractor, - appGraph.analyticComponent.analyticInteractor, - appGraph.sentryComponent.sentryInteractor, - appGraph.loggerComponent.logger, - appGraph.commonComponent.buildKonfig.buildVariant + override val stepQuizHintsReducer: StepQuizHintsReducer + get() = StepQuizHintsReducer(stepRoute = stepRoute) + + override val stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher + get() = StepQuizHintsActionDispatcher( + config = ActionDispatcherOptions(), + stepQuizHintsInteractor = stepQuizHintsInteractor, + likesInteractor = appGraph.buildLikesDataComponent().likesInteractor, + commentsInteractor = appGraph.buildCommentsDataComponent().commentsInteractor, + reactionsInteractor = appGraph.buildReactionsDataComponent().reactionsInteractor, + userStorageInteractor = appGraph.buildUserStorageComponent().userStorageInteractor, + freemiumInteractor = appGraph.buildFreemiumDataComponent().freemiumInteractor, + analyticInteractor = appGraph.analyticComponent.analyticInteractor, + sentryInteractor = appGraph.sentryComponent.sentryInteractor ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsFeatureBuilder.kt deleted file mode 100644 index ddb213b1e5..0000000000 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/injection/StepQuizHintsFeatureBuilder.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.hyperskill.app.step_quiz_hints.injection - -import co.touchlab.kermit.Logger -import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor -import org.hyperskill.app.comments.domain.interactor.CommentsInteractor -import org.hyperskill.app.core.domain.BuildVariant -import org.hyperskill.app.core.presentation.ActionDispatcherOptions -import org.hyperskill.app.core.presentation.transformState -import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor -import org.hyperskill.app.likes.domain.interactor.LikesInteractor -import org.hyperskill.app.logging.presentation.wrapWithLogger -import org.hyperskill.app.reactions.domain.interactor.ReactionsInteractor -import org.hyperskill.app.sentry.domain.interactor.SentryInteractor -import org.hyperskill.app.step.domain.model.StepRoute -import org.hyperskill.app.step_quiz_hints.domain.interactor.StepQuizHintsInteractor -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispatcher -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer -import org.hyperskill.app.step_quiz_hints.view.mapper.StepQuizHintsViewStateMapper -import org.hyperskill.app.user_storage.domain.interactor.UserStorageInteractor -import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher -import ru.nobird.app.presentation.redux.feature.Feature -import ru.nobird.app.presentation.redux.feature.ReduxFeature - -object StepQuizHintsFeatureBuilder { - private const val LOG_TAG = "StepQuizHintsFeature" - - fun build( - stepRoute: StepRoute, - stepQuizHintsInteractor: StepQuizHintsInteractor, - likesInteractor: LikesInteractor, - commentsInteractor: CommentsInteractor, - reactionsInteractor: ReactionsInteractor, - userStorageInteractor: UserStorageInteractor, - freemiumInteractor: FreemiumInteractor, - analyticInteractor: AnalyticInteractor, - sentryInteractor: SentryInteractor, - logger: Logger, - buildVariant: BuildVariant - ): Feature { - val stepQuizHintsReducer = StepQuizHintsReducer(stepRoute).wrapWithLogger(buildVariant, logger, LOG_TAG) - - val stepQuizHintsDispatcher = StepQuizHintsActionDispatcher( - ActionDispatcherOptions(), - stepQuizHintsInteractor, - likesInteractor, - commentsInteractor, - reactionsInteractor, - userStorageInteractor, - freemiumInteractor, - analyticInteractor, - sentryInteractor - ) - - return ReduxFeature(StepQuizHintsFeature.State.Idle, stepQuizHintsReducer) - .transformState(StepQuizHintsViewStateMapper::mapState) - .wrapWithActionDispatcher(stepQuizHintsDispatcher) - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt index 3ecbf2f2b6..44c7b926d7 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsActionDispatcher.kt @@ -17,7 +17,7 @@ import org.hyperskill.app.user_storage.domain.interactor.UserStorageInteractor import org.hyperskill.app.user_storage.domain.model.UserStoragePathBuilder import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher -internal class StepQuizHintsActionDispatcher( +class StepQuizHintsActionDispatcher( config: ActionDispatcherOptions, private val stepQuizHintsInteractor: StepQuizHintsInteractor, private val likesInteractor: LikesInteractor, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt index 06a4442cbe..b7edb66e57 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsFeature.kt @@ -10,7 +10,7 @@ object StepQuizHintsFeature { fun isHintsFeatureAvailable(step: Step): Boolean = step.commentsStatistics.any { it.thread == CommentThread.HINT && it.totalCount > 0 } - internal sealed interface State { + sealed interface State { object Idle : State /** diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt index ea3bb4cec1..d877d1961c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/presentation/StepQuizHintsReducer.kt @@ -11,7 +11,7 @@ import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.Mess import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature.State import ru.nobird.app.presentation.redux.reducer.StateReducer -internal class StepQuizHintsReducer(private val stepRoute: StepRoute) : StateReducer { +class StepQuizHintsReducer(private val stepRoute: StepRoute) : StateReducer { override fun reduce(state: State, message: Message): Pair> = when (message) { is Message.InitWithStepId -> diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/view/mapper/StepQuizHintsViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/view/mapper/StepQuizHintsViewStateMapper.kt index 5539b904e1..9f0432b923 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/view/mapper/StepQuizHintsViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_hints/view/mapper/StepQuizHintsViewStateMapper.kt @@ -2,7 +2,7 @@ package org.hyperskill.app.step_quiz_hints.view.mapper import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature -internal object StepQuizHintsViewStateMapper { +object StepQuizHintsViewStateMapper { fun mapState(state: StepQuizHintsFeature.State): StepQuizHintsFeature.ViewState = when (state) { is StepQuizHintsFeature.State.Idle -> StepQuizHintsFeature.ViewState.Idle diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/injection/StudyPlanWidgetComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/injection/StudyPlanWidgetComponentImpl.kt index f79cc734cb..0c2b627a5e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/injection/StudyPlanWidgetComponentImpl.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/injection/StudyPlanWidgetComponentImpl.kt @@ -14,6 +14,7 @@ class StudyPlanWidgetComponentImpl(private val appGraph: AppGraph) : StudyPlanWi trackInteractor = appGraph.buildTrackDataComponent().trackInteractor, nextLearningActivityStateRepository = appGraph .stateRepositoriesComponent.nextLearningActivityStateRepository, + currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository, sentryInteractor = appGraph.sentryComponent.sentryInteractor, analyticInteractor = appGraph.analyticComponent.analyticInteractor ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt index b9d61698cd..6ec7f74fbb 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetActionDispatcher.kt @@ -4,8 +4,10 @@ import kotlinx.coroutines.delay import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository +import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder +import org.hyperskill.app.sentry.domain.withTransaction import org.hyperskill.app.study_plan.domain.interactor.StudyPlanInteractor import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.Action import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.InternalAction @@ -18,6 +20,7 @@ class StudyPlanWidgetActionDispatcher( private val studyPlanInteractor: StudyPlanInteractor, private val trackInteractor: TrackInteractor, private val nextLearningActivityStateRepository: NextLearningActivityStateRepository, + private val currentProfileStateRepository: CurrentProfileStateRepository, private val sentryInteractor: SentryInteractor, private val analyticInteractor: AnalyticInteractor ) : CoroutineActionDispatcher(config.createConfig()) { @@ -95,6 +98,18 @@ class StudyPlanWidgetActionDispatcher( onNewMessage(StudyPlanWidgetFeature.TrackFetchResult.Failed) } } + is InternalAction.FetchProfile -> { + sentryInteractor.withTransaction( + HyperskillSentryTransactionBuilder.buildStudyPlanWidgetFetchProfile(), + onError = { StudyPlanWidgetFeature.ProfileFetchResult.Failed } + ) { + currentProfileStateRepository + .getState() + .getOrThrow() + .let(StudyPlanWidgetFeature.ProfileFetchResult::Success) + }.let(::onNewMessage) + } + is InternalAction.UpdateNextLearningActivityState -> { nextLearningActivityStateRepository.updateState(newState = action.learningActivity) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt index 556af76bf9..6d584c758c 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFeature.kt @@ -7,6 +7,7 @@ import org.hyperskill.app.learning_activities.domain.model.LearningActivity import org.hyperskill.app.learning_activities.domain.model.LearningActivityState import org.hyperskill.app.learning_activities.domain.model.LearningActivityType import org.hyperskill.app.learning_activities.presentation.model.LearningActivityTargetViewAction +import org.hyperskill.app.profile.domain.model.Profile import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransaction import org.hyperskill.app.study_plan.domain.model.StudyPlan import org.hyperskill.app.study_plan.domain.model.StudyPlanSection @@ -35,7 +36,12 @@ object StudyPlanWidgetFeature { /** * Pull to refresh flag */ - val isRefreshing: Boolean = false + val isRefreshing: Boolean = false, + + /** + * Divided track topics feature enabled flag + */ + val isLearningPathDividedTrackTopicsEnabled: Boolean = false ) enum class ContentStatus { @@ -107,6 +113,12 @@ object StudyPlanWidgetFeature { object Failed : TrackFetchResult } + internal sealed interface ProfileFetchResult : Message { + data class Success(val profile: Profile) : ProfileFetchResult + + object Failed : ProfileFetchResult + } + sealed interface Action { sealed interface ViewAction : Action { sealed interface NavigateTo : ViewAction { @@ -141,6 +153,8 @@ object StudyPlanWidgetFeature { data class FetchTrack(val trackId: Long) : InternalAction + object FetchProfile : InternalAction + data class UpdateNextLearningActivityState(val learningActivity: LearningActivity?) : InternalAction data class CaptureSentryException(val throwable: Throwable) : InternalAction diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt index 71d4087fc4..bfc83e7652 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetReducer.kt @@ -4,6 +4,7 @@ import kotlin.math.max import kotlin.math.min import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute import org.hyperskill.app.learning_activities.presentation.mapper.LearningActivityTargetViewActionMapper +import org.hyperskill.app.profile.domain.model.isLearningPathDividedTrackTopicsEnabled import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedActivityHyperskillAnalyticEvent import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryActivitiesLoadingHyperskillAnalyticEvent @@ -73,6 +74,15 @@ class StudyPlanWidgetReducer : StateReducer { is StudyPlanWidgetFeature.TrackFetchResult.Failed -> { null } + is StudyPlanWidgetFeature.ProfileFetchResult.Success -> { + state.copy( + isLearningPathDividedTrackTopicsEnabled = + message.profile.features.isLearningPathDividedTrackTopicsEnabled + ) to emptySet() + } + is StudyPlanWidgetFeature.ProfileFetchResult.Failed -> { + null + } is Message.SectionClicked -> changeSectionExpanse(state, message.sectionId, shouldLogAnalyticEvent = true) is Message.ActivityClicked -> @@ -109,7 +119,7 @@ class StudyPlanWidgetReducer : StateReducer { state.sectionsStatus == StudyPlanWidgetFeature.ContentStatus.ERROR && message.forceUpdate ) { State(sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADING) to - setOf(InternalAction.FetchStudyPlan()) + setOf(InternalAction.FetchStudyPlan(), InternalAction.FetchProfile) } else { state to emptySet() } @@ -133,7 +143,7 @@ class StudyPlanWidgetReducer : StateReducer { } return if (message.showLoadingIndicators) { - State( + state.copy( studyPlan = message.studyPlan, sectionsStatus = StudyPlanWidgetFeature.ContentStatus.LOADING ) to actions @@ -249,7 +259,10 @@ class StudyPlanWidgetReducer : StateReducer { ) to setOf( InternalAction.FetchActivities( sectionId = message.sectionId, - activitiesIds = getPaginatedActivitiesIds(section), + activitiesIds = getPaginatedActivitiesIds( + section = section, + isLearningPathDividedTrackTopicsEnabled = state.isLearningPathDividedTrackTopicsEnabled + ), sentryTransaction = HyperskillSentryTransactionBuilder.buildStudyPlanWidgetFetchLearningActivities( isCurrentSection = message.sectionId == state.getCurrentSection()?.id ) @@ -294,7 +307,10 @@ class StudyPlanWidgetReducer : StateReducer { updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOfNotNull( InternalAction.FetchActivities( sectionId = sectionId, - activitiesIds = getPaginatedActivitiesIds(section), + activitiesIds = getPaginatedActivitiesIds( + section = section, + isLearningPathDividedTrackTopicsEnabled = state.isLearningPathDividedTrackTopicsEnabled + ), sentryTransaction = sentryTransaction ), logAnalyticEventAction @@ -312,9 +328,13 @@ class StudyPlanWidgetReducer : StateReducer { } } - internal fun getPaginatedActivitiesIds(section: StudyPlanWidgetFeature.StudyPlanSectionInfo): List = + internal fun getPaginatedActivitiesIds( + section: StudyPlanWidgetFeature.StudyPlanSectionInfo, + isLearningPathDividedTrackTopicsEnabled: Boolean + ): List = if (section.studyPlanSection.type == StudyPlanSectionType.ROOT_TOPICS && - section.studyPlanSection.nextActivityId != null + section.studyPlanSection.nextActivityId != null && + !isLearningPathDividedTrackTopicsEnabled ) { val startIndex = max(0, section.studyPlanSection.activities.indexOf(section.studyPlanSection.nextActivityId)) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt index 03bb9f1628..8ad1bed7c4 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt @@ -9,6 +9,8 @@ import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbar import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizReducer +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature +import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer import org.hyperskill.step.domain.model.stub import org.hyperskill.step_quiz.domain.model.stub @@ -27,14 +29,19 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = false - ) + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle ) stepRoutes.forEach { stepRoute -> - val reducer = StepQuizReducer(stepRoute) + val reducer = StepQuizReducer( + stepRoute = stepRoute, + stepQuizHintsReducer = StepQuizHintsReducer(stepRoute) + ) val (state, actions) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading + stepQuizState = StepQuizFeature.StepQuizState.Loading, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -64,15 +71,18 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = true, isTheoryAvailable = false - ) + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle ) val reducer = StepQuizReducer( - stepRoute = StepRoute.Learn.Step(step.id) + stepRoute = StepRoute.Learn.Step(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.Learn.Step(step.id)) ) val (state, actions) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading + stepQuizState = StepQuizFeature.StepQuizState.Loading, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -104,16 +114,19 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = true - ) + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle ) val reducer = StepQuizReducer( - StepRoute.Learn.Step(step.id) + stepRoute = StepRoute.Learn.Step(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.Learn.Step(step.id)) ) val (intermediateState, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading + stepQuizState = StepQuizFeature.StepQuizState.Loading, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -159,16 +172,19 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = false - ) + ), + stepQuizHintsState = StepQuizHintsFeature.State.Idle ) val reducer = StepQuizReducer( - StepRoute.LearnDaily(step.id) + stepRoute = StepRoute.LearnDaily(step.id), + stepQuizHintsReducer = StepQuizHintsReducer(StepRoute.Learn.Step(step.id)) ) val (intermediateState, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading + stepQuizState = StepQuizFeature.StepQuizState.Loading, + stepQuizHintsState = StepQuizHintsFeature.State.Idle ), StepQuizFeature.Message.FetchAttemptSuccess( step, diff --git a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/widget/StudyPlanWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/widget/StudyPlanWidgetTest.kt index 859cb684b5..a210c6b7f8 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/widget/StudyPlanWidgetTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/widget/StudyPlanWidgetTest.kt @@ -1094,7 +1094,10 @@ class StudyPlanWidgetTest { contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) - assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = false) + ) } @Test @@ -1110,7 +1113,10 @@ class StudyPlanWidgetTest { contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) - assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = false) + ) } @Test @@ -1127,7 +1133,10 @@ class StudyPlanWidgetTest { contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) - assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = false) + ) } @Test @@ -1144,7 +1153,10 @@ class StudyPlanWidgetTest { contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) - assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = false) + ) } @Test @@ -1161,7 +1173,30 @@ class StudyPlanWidgetTest { contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED ) - assertEquals(expectedActivitiesIds, reducer.getPaginatedActivitiesIds(section)) + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = false) + ) + } + + @Test + fun `Get not paginated activities ids in root topics section when DividedTrackTopics feature enabled`() { + val expectedActivitiesIds = (0L..50L).toList() + val section = StudyPlanWidgetFeature.StudyPlanSectionInfo( + studyPlanSection = studyPlanSectionStub( + id = 0, + type = StudyPlanSectionType.ROOT_TOPICS, + activities = expectedActivitiesIds, + nextActivityId = 5L + ), + isExpanded = false, + contentStatus = StudyPlanWidgetFeature.ContentStatus.LOADED + ) + + assertEquals( + expectedActivitiesIds, + reducer.getPaginatedActivitiesIds(section, isLearningPathDividedTrackTopicsEnabled = true) + ) } @Test