From 6486390399fbc4d9123028da690b17341b67525a Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 16 Sep 2024 09:21:32 +0000
Subject: [PATCH 01/27] Set version number to 1.72

---
 gradle/app.versions.toml                                 | 2 +-
 iosHyperskillApp/NotificationServiceExtension/Info.plist | 2 +-
 iosHyperskillApp/iosHyperskillApp/Info.plist             | 2 +-
 iosHyperskillApp/iosHyperskillAppTests/Info.plist        | 2 +-
 iosHyperskillApp/iosHyperskillAppUITests/Info.plist      | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 66197c498..375166ae7 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -2,5 +2,5 @@
 minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
-versionName = '1.71'
+versionName = '1.72'
 versionCode = '547'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index 43d555cbd..b1dfd66fb 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -11,7 +11,7 @@
 	<key>CFBundleVersion</key>
 	<string>576</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.71</string>
+	<string>1.72</string>
 	<key>CFBundlePackageType</key>
 	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
 	<key>CFBundleExecutable</key>
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index cf90b39b3..47b44ca65 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -21,7 +21,7 @@
 	<key>CFBundlePackageType</key>
 	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.71</string>
+	<string>1.72</string>
 	<key>CFBundleURLTypes</key>
 	<array>
 		<dict>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 8348e40f1..08eb0113e 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -13,7 +13,7 @@
 	<key>CFBundlePackageType</key>
 	<string>BNDL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.71</string>
+	<string>1.72</string>
 	<key>CFBundleVersion</key>
 	<string>576</string>
 </dict>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 47e97daaa..68baab904 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -13,7 +13,7 @@
 	<key>CFBundlePackageType</key>
 	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.71</string>
+	<string>1.72</string>
 	<key>CFBundleVersion</key>
 	<string>576</string>
 </dict>

From 3215e6dfd2a2769be2fb6d6d6d5aab2a638a5951 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 16 Sep 2024 09:21:34 +0000
Subject: [PATCH 02/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 375166ae7..6f32ffb48 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '547'
\ No newline at end of file
+versionCode = '548'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index b1dfd66fb..d269857f3 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>576</string>
+	<string>577</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index aa2b33102..58e862c40 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 576;
+				CURRENT_PROJECT_VERSION = 577;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 47b44ca65..730498d2b 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>576</string>
+	<string>577</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 08eb0113e..f046c6eda 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>576</string>
+	<string>577</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 68baab904..cd2d438df 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>576</string>
+	<string>577</string>
 </dict>
 </plist>

From 67cd51eed337af16f6acb2ab69127b6423d62f9d Mon Sep 17 00:00:00 2001
From: Aleksandr Zhukov <alexandr.zhukov@hyperskill.org>
Date: Mon, 16 Sep 2024 12:33:07 +0200
Subject: [PATCH 03/27] Shared, Android code execution (#1180)

---
 .../StepQuizFeedbackBlocksDelegate.kt         | 115 ++++++++----
 .../view/model/config/CommonCodeQuizConfig.kt |   4 +-
 .../layout/fragment_step_quiz_unsupported.xml |   2 +-
 .../layout_step_quiz_feedback_block.xml       |  59 +++++-
 .../Shared/Model/BlockOptionsExtensions.swift |   2 +-
 .../Views/Feedback/StepQuizFeedbackView.swift |  26 ++-
 .../StepQuizFeedbackWrongStateView.swift      |   6 +-
 .../ViewData/StepQuizCodeViewDataMapper.swift |  11 +-
 .../data/repository/CodeRepositoryImpl.kt     |  15 ++
 .../code/data/source/CodeRemoteDataSource.kt  |   8 +
 .../code/domain/model/CodeExecutionResult.kt  |  18 ++
 .../code/domain/repository/CodeRepository.kt  |   8 +
 .../app/code/injection/CodeDataComponent.kt   |   7 +
 .../code/injection/CodeDataComponentImpl.kt   |  15 ++
 .../code/remote/CodeRemoteDataSourceImpl.kt   |  23 +++
 .../app/code/remote/model/RunCodeRequest.kt   |  14 ++
 .../app/code/remote/model/RunCodeResponse.kt  |  15 ++
 .../hyperskill/app/core/injection/AppGraph.kt |   2 +
 .../app/core/injection/BaseAppGraph.kt        |   5 +
 .../hyperskill/app/step/domain/model/Block.kt |  15 +-
 .../injection/StepQuizComponentImpl.kt        |   1 +
 .../injection/StepQuizFeatureBuilder.kt       |   3 +
 .../presentation/StepQuizActionDispatcher.kt  | 177 ++++++++++++------
 .../step_quiz/presentation/StepQuizFeature.kt |  10 +-
 .../step_quiz/presentation/StepQuizReducer.kt |   3 +-
 .../view/mapper/StepQuizFeedbackMapper.kt     |  67 +++++--
 .../view/model/StepQuizFeedbackState.kt       |  25 ++-
 .../moko-resources/base/strings.xml           |   3 +
 .../step_quiz/StepQuizFeedbackMapperTest.kt   |  62 +++++-
 29 files changed, 581 insertions(+), 140 deletions(-)
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt

diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
index fc5fd2e60..d25984e41 100644
--- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
+++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
@@ -24,33 +24,33 @@ class StepQuizFeedbackBlocksDelegate(
         private const val NON_LATEX_HINT_TEMPLATE = """<pre><span style="font-family: 'Roboto';">%s</span></pre>"""
     }
 
-    private val viewStateDelegate = ViewStateDelegate<StepQuizFeedbackState>()
+    private val viewStateDelegate = ViewStateDelegate<StepQuizFeedbackState>().apply {
+        addState<StepQuizFeedbackState.Idle>()
+        addState<StepQuizFeedbackState.UnsupportedStep>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackUnsupported
+        )
+        addState<StepQuizFeedbackState.Evaluation>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackEvaluation,
+            layoutStepQuizFeedbackBlockBinding.stepQuizCodeExecutionHint
+        )
+        addState<StepQuizFeedbackState.Correct>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackCorrect,
+            layoutStepQuizFeedbackBlockBinding.stepQuizSubmissionHint,
+            layoutStepQuizFeedbackBlockBinding.stepQuizCodeExecutionHint
+        )
+        addState<StepQuizFeedbackState.Wrong>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackWrong,
+            layoutStepQuizFeedbackBlockBinding.stepQuizSubmissionHint
+        )
+        addState<StepQuizFeedbackState.ValidationFailed>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation
+        )
+        addState<StepQuizFeedbackState.RejectedSubmission>(
+            layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation
+        )
+    }
 
     init {
-        with(viewStateDelegate) {
-            addState<StepQuizFeedbackState.Idle>()
-            addState<StepQuizFeedbackState.UnsupportedStep>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackUnsupported
-            )
-            addState<StepQuizFeedbackState.Evaluation>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackEvaluation
-            )
-            addState<StepQuizFeedbackState.Correct>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackCorrect,
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedback
-            )
-            addState<StepQuizFeedbackState.Wrong>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackWrong,
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedback
-            )
-            addState<StepQuizFeedbackState.ValidationFailed>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation
-            )
-            addState<StepQuizFeedbackState.RejectedSubmission>(
-                layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackValidation
-            )
-        }
-
         getEvaluationDrawable(context).let { evaluationDrawable ->
             layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackEvaluation
                 .setCompoundDrawablesWithIntrinsicBounds(evaluationDrawable, null, null, null)
@@ -73,8 +73,11 @@ class StepQuizFeedbackBlocksDelegate(
         viewStateDelegate.switchState(state)
         layoutStepQuizFeedbackBlockBinding.root.isVisible = state !is StepQuizFeedbackState.Idle
         when (state) {
+            is StepQuizFeedbackState.Evaluation -> {
+                setHint(state.hint, layoutStepQuizFeedbackBlockBinding)
+            }
             is StepQuizFeedbackState.Correct -> {
-                setHint(layoutStepQuizFeedbackBlockBinding, state.hint, state.useLatex)
+                setHint(state.hint, layoutStepQuizFeedbackBlockBinding)
             }
             is StepQuizFeedbackState.Wrong -> {
                 with(layoutStepQuizFeedbackBlockBinding) {
@@ -103,7 +106,7 @@ class StepQuizFeedbackBlocksDelegate(
                         } else null
                     )
 
-                    setHint(layoutStepQuizFeedbackBlockBinding, state.feedbackHint, state.useFeedbackHintLatex)
+                    setHint(state.hint, layoutStepQuizFeedbackBlockBinding)
                 }
             }
             is StepQuizFeedbackState.ValidationFailed -> {
@@ -119,21 +122,61 @@ class StepQuizFeedbackBlocksDelegate(
     }
 
     private fun setHint(
-        layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding,
-        hint: String?,
-        useLatex: Boolean
+        hint: StepQuizFeedbackState.Hint?,
+        layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding
     ) {
-        val resultHint = hint?.let {
-            if (useLatex) {
-                hint
-            } else {
-                String.format(NON_LATEX_HINT_TEMPLATE, hint)
+        layoutStepQuizFeedbackBlockBinding.stepQuizSubmissionHint.isVisible =
+            hint is StepQuizFeedbackState.Hint.FromSubmission
+        layoutStepQuizFeedbackBlockBinding.stepQuizCodeExecutionHint.isVisible =
+            hint is StepQuizFeedbackState.Hint.FromCodeExecution
+        when (hint) {
+            is StepQuizFeedbackState.Hint.FromSubmission ->
+                setRemoteHint(hint, layoutStepQuizFeedbackBlockBinding)
+            is StepQuizFeedbackState.Hint.FromCodeExecution ->
+                setCodeExecutionHint(hint, layoutStepQuizFeedbackBlockBinding)
+            null -> {
+                // no op
             }
         }
-        layoutStepQuizFeedbackBlockBinding.stepQuizFeedback.isVisible = resultHint != null
+    }
+
+    private fun setRemoteHint(
+        hint: StepQuizFeedbackState.Hint.FromSubmission,
+        layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding
+    ) {
+        val resultHint = if (hint.useLatex) {
+            hint.text
+        } else {
+            String.format(NON_LATEX_HINT_TEMPLATE, hint.text)
+        }
         layoutStepQuizFeedbackBlockBinding.stepQuizFeedbackBody.setText(resultHint)
     }
 
+    private fun setCodeExecutionHint(
+        hint: StepQuizFeedbackState.Hint.FromCodeExecution,
+        layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding
+    ) {
+        with(layoutStepQuizFeedbackBlockBinding) {
+            val isInputVisible =
+                hint is StepQuizFeedbackState.Hint.FromCodeExecution.Result && hint.input != null
+            stepQuizCodeExecutionInputTitleTextView.isVisible = isInputVisible
+            stepQuizCodeExecutionInputValueTextView.isVisible = isInputVisible
+            if (isInputVisible) {
+                stepQuizCodeExecutionInputValueTextView.text =
+                    (hint as? StepQuizFeedbackState.Hint.FromCodeExecution.Result)?.input
+            }
+
+            val isOutputVisible =
+                hint is StepQuizFeedbackState.Hint.FromCodeExecution.Result
+            stepQuizCodeExecutionOutputTitleTextView.isVisible = isOutputVisible
+            stepQuizCodeExecutionOutputValueTextView.isVisible = isOutputVisible
+            if (isOutputVisible) {
+                stepQuizCodeExecutionOutputValueTextView.text =
+                    (hint as? StepQuizFeedbackState.Hint.FromCodeExecution.Result)?.output
+            }
+        }
+    }
+
     private fun getEvaluationDrawable(context: Context): AnimationDrawable =
         AnimationDrawable().apply {
             addFrame(
diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_code/view/model/config/CommonCodeQuizConfig.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_code/view/model/config/CommonCodeQuizConfig.kt
index 34c5be47d..9abf70ba6 100644
--- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_code/view/model/config/CommonCodeQuizConfig.kt
+++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_code/view/model/config/CommonCodeQuizConfig.kt
@@ -21,8 +21,8 @@ class CommonCodeQuizConfig(private val step: Step) : CodeStepQuizConfig {
             ?.mapIndexed { i, sample ->
                 CodeDetail.Sample(
                     i + 1,
-                    sample.first().trim('\n'),
-                    sample.last().trim('\n')
+                    sample.input.trim('\n'),
+                    sample.output.trim('\n')
                 )
             } ?: emptyList()
 
diff --git a/androidHyperskillApp/src/main/res/layout/fragment_step_quiz_unsupported.xml b/androidHyperskillApp/src/main/res/layout/fragment_step_quiz_unsupported.xml
index 134ef3739..9a91094aa 100644
--- a/androidHyperskillApp/src/main/res/layout/fragment_step_quiz_unsupported.xml
+++ b/androidHyperskillApp/src/main/res/layout/fragment_step_quiz_unsupported.xml
@@ -10,7 +10,7 @@
     tools:viewBindingIgnore="true">
 
     <com.google.android.material.textview.MaterialTextView
-        android:id="@+id/stepQuizFeedback"
+        android:id="@+id/stepQuizSubmissionHint"
         style="@style/StepQuizFeedback.Wrong"
         android:text="Current quiz type is not supported in mobile app yet"
         android:layout_margin="16dp"
diff --git a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml
index 6b52c206d..36b7eb04c 100644
--- a/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml
+++ b/androidHyperskillApp/src/main/res/layout/layout_step_quiz_feedback_block.xml
@@ -76,7 +76,7 @@
         android:text="@string/step_quiz_status_correct_text" />
 
     <com.google.android.material.card.MaterialCardView
-        android:id="@+id/stepQuizFeedback"
+        android:id="@+id/stepQuizSubmissionHint"
         android:layout_height="wrap_content"
         style="@style/StepQuizFeedback.Feedback"
         android:background="@color/color_background"
@@ -123,4 +123,61 @@
 
     </com.google.android.material.card.MaterialCardView>
 
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/stepQuizCodeExecutionHint"
+        android:layout_height="wrap_content"
+        style="@style/StepQuizFeedback.Feedback"
+        android:background="@color/color_background"
+        android:maxWidth="@dimen/auth_button_max_width"
+        android:layout_marginTop="20dp">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="16dp"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/stepQuizCodeExecutionTitleTextView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.AppCompat.Body1"
+                android:text="@string/step_quiz_code_execution_title"
+                android:layout_marginBottom="7dp"/>
+
+            <TextView
+                android:id="@+id/stepQuizCodeExecutionInputTitleTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.AppCompat.Body1"
+                android:text="@string/step_quiz_code_execution_input"
+                android:layout_marginBottom="7dp"/>
+
+            <TextView
+                android:id="@+id/stepQuizCodeExecutionInputValueTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.AppCompat.Body1"
+                tools:text="5"
+                android:layout_marginBottom="7dp"/>
+
+            <TextView
+                android:id="@+id/stepQuizCodeExecutionOutputTitleTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.AppCompat.Body1"
+                android:text="@string/step_quiz_code_execution_output"
+                android:layout_marginBottom="7dp"/>
+
+            <TextView
+                android:id="@+id/stepQuizCodeExecutionOutputValueTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.AppCompat.Body1"
+                tools:text="120" />
+
+        </LinearLayout>
+
+    </com.google.android.material.card.MaterialCardView>
+
 </androidx.appcompat.widget.LinearLayoutCompat>
\ No newline at end of file
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
index 86f0364e3..68abcdd22 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
@@ -23,7 +23,7 @@ extension Block.Options {
             isCheckbox: isCheckbox.flatMap(KotlinBoolean.init(value:)),
             limits: limits,
             codeTemplates: codeTemplates,
-            samples: samples,
+            internalSamples: samples,
             files: files,
             codeBlanksStrings: codeBlanksStrings,
             codeBlanksVariables: codeBlanksVariables,
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
index 615f0aad6..e1f389486 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
@@ -14,7 +14,13 @@ struct StepQuizFeedbackView: View {
             StepQuizFeedbackStatusView(state: .correct)
 
             if let hint = correctState.hint {
-                StepQuizFeedbackHintView(text: hint)
+                let hintKs = StepQuizFeedbackStateHintKs(hint)
+                switch hintKs {
+                case .fromCodeExecution:
+                    #warning("TODO: ALTAPPS-1358")
+                case .fromSubmission(let fromSubmission):
+                    StepQuizFeedbackHintView(text: fromSubmission.text)
+                }
             }
         case .wrong(let wrongState):
             StepQuizFeedbackWrongStateView(
@@ -22,8 +28,14 @@ struct StepQuizFeedbackView: View {
                 onAction: onAction
             )
 
-            if let feedbackHint = wrongState.feedbackHint {
-                StepQuizFeedbackHintView(text: feedbackHint)
+            if let feedbackHint = wrongState.hint {
+                let hintKs = StepQuizFeedbackStateHintKs(feedbackHint)
+                switch hintKs {
+                case .fromCodeExecution:
+                    #warning("TODO: ALTAPPS-1358")
+                case .fromSubmission(let fromSubmission):
+                    StepQuizFeedbackHintView(text: fromSubmission.text)
+                }
             }
         case .rejectedSubmission(let rejectedSubmissionState):
             StepQuizFeedbackStatusView(
@@ -50,10 +62,12 @@ struct StepQuizFeedbackView: View {
             StepQuizFeedbackView(
                 stepQuizFeedbackState: .correct(
                     StepQuizFeedbackStateCorrect(
-                        hint: """
+                        hint: StepQuizFeedbackStateHintFromSubmission(
+                            text: """
 That's right! Since any comparison results in a boolean value, there is no need to write everything twice.
 """,
-                        useLatex: false
+                            useLatex: false
+                        )
                     )
                 ),
                 onAction: { _ in }
@@ -69,7 +83,7 @@ That's right! Since any comparison results in a boolean value, there is no need
             )
 
             StepQuizFeedbackView(
-                stepQuizFeedbackState: .evaluation,
+                stepQuizFeedbackState: .evaluation(StepQuizFeedbackStateEvaluation(hint: nil)),
                 onAction: { _ in }
             )
 
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackWrongStateView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackWrongStateView.swift
index 136d92925..1b58f2347 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackWrongStateView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackWrongStateView.swift
@@ -67,8 +67,7 @@ let previewTitle = "Not correct"
             description: nil,
             actionText: nil,
             actionType: nil,
-            feedbackHint: nil,
-            useFeedbackHintLatex: false
+            hint: nil
         ),
         onAction: { _ in }
     )
@@ -82,8 +81,7 @@ let previewTitle = "Not correct"
             description: "Take a look at what other users are suggesting to solve this problem.",
             actionText: "See community hint",
             actionType: nil,
-            feedbackHint: nil,
-            useFeedbackHintLatex: false
+            hint: nil
         ),
         onAction: { _ in }
     )
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift
index 450fc6006..8d7d83758 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift
@@ -54,7 +54,7 @@ class StepQuizCodeViewDataMapper {
 
     // MARK: Private API
 
-    private func mapSamples(_ samples: [[String]]?) -> [StepQuizCodeViewData.Sample] {
+    private func mapSamples(_ samples: [Block.OptionsSample]?) -> [StepQuizCodeViewData.Sample] {
         guard let samples else {
             return []
         }
@@ -63,11 +63,6 @@ class StepQuizCodeViewDataMapper {
         var displayIndex = 1
 
         for sample in samples {
-            guard let input = sample.first,
-                  let output = sample.last else {
-                continue
-            }
-
             let inputTitle = resourceProvider.getString(
                 stringResource: Strings.StepQuizCode.sampleInputTitleResource,
                 args: KotlinArray(size: 1, init: { _ in NSNumber(value: displayIndex) })
@@ -80,9 +75,9 @@ class StepQuizCodeViewDataMapper {
             result.append(
                 .init(
                     inputTitle: inputTitle,
-                    inputValue: input.trimmed(),
+                    inputValue: sample.input.trimmed(),
                     outputTitle: outputTitle,
-                    outputValue: output.trimmed()
+                    outputValue: sample.output.trimmed()
                 )
             )
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
new file mode 100644
index 000000000..c58994d78
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.code.data.repository
+
+import org.hyperskill.app.code.data.source.CodeRemoteDataSource
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
+import org.hyperskill.app.code.domain.repository.CodeRepository
+import org.hyperskill.app.code.remote.model.RunCodeRequest
+
+internal class CodeRepositoryImpl(
+    private val codeRemoteDataSource: CodeRemoteDataSource
+) : CodeRepository {
+    override suspend fun runCode(runCodeRequest: RunCodeRequest): Result<CodeExecutionResult> =
+        codeRemoteDataSource
+            .runCode(runCodeRequest)
+            .mapCatching { it.codeExecutionResults.first() }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
new file mode 100644
index 000000000..cb09df3d8
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
@@ -0,0 +1,8 @@
+package org.hyperskill.app.code.data.source
+
+import org.hyperskill.app.code.remote.model.RunCodeRequest
+import org.hyperskill.app.code.remote.model.RunCodeResponse
+
+interface CodeRemoteDataSource {
+    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeResponse>
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt
new file mode 100644
index 000000000..290cfe958
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt
@@ -0,0 +1,18 @@
+package org.hyperskill.app.code.domain.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CodeExecutionResult(
+    @SerialName("language")
+    val language: String,
+    @SerialName("stdin")
+    val stdin: String?,
+    @SerialName("is_success")
+    val isSuccess: Boolean,
+    @SerialName("stdout")
+    val stdout: String? = null,
+    @SerialName("stderr")
+    val stderr: String? = null
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
new file mode 100644
index 000000000..1934e9266
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
@@ -0,0 +1,8 @@
+package org.hyperskill.app.code.domain.repository
+
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
+import org.hyperskill.app.code.remote.model.RunCodeRequest
+
+interface CodeRepository {
+    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<CodeExecutionResult>
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
new file mode 100644
index 000000000..197945cc2
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
@@ -0,0 +1,7 @@
+package org.hyperskill.app.code.injection
+
+import org.hyperskill.app.code.domain.repository.CodeRepository
+
+interface CodeDataComponent {
+    val codeRepository: CodeRepository
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
new file mode 100644
index 000000000..e7fbd5fc1
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.code.injection
+
+import org.hyperskill.app.code.data.repository.CodeRepositoryImpl
+import org.hyperskill.app.code.data.source.CodeRemoteDataSource
+import org.hyperskill.app.code.domain.repository.CodeRepository
+import org.hyperskill.app.code.remote.CodeRemoteDataSourceImpl
+import org.hyperskill.app.core.injection.AppGraph
+
+internal class CodeDataComponentImpl(appGraph: AppGraph) : CodeDataComponent {
+
+    private val codeRemoteDataSource: CodeRemoteDataSource =
+        CodeRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient)
+
+    override val codeRepository: CodeRepository = CodeRepositoryImpl(codeRemoteDataSource)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt
new file mode 100644
index 000000000..ef783f4e6
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt
@@ -0,0 +1,23 @@
+package org.hyperskill.app.code.remote
+
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
+import org.hyperskill.app.code.data.source.CodeRemoteDataSource
+import org.hyperskill.app.code.remote.model.RunCodeRequest
+import org.hyperskill.app.code.remote.model.RunCodeResponse
+
+internal class CodeRemoteDataSourceImpl(private val httpClient: HttpClient) : CodeRemoteDataSource {
+    override suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeResponse> =
+        runCatching {
+            httpClient
+                .post("/api/run-code") {
+                    contentType(ContentType.Application.Json)
+                    setBody(runCodeRequest)
+                }
+                .body()
+        }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt
new file mode 100644
index 000000000..56b6c2802
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt
@@ -0,0 +1,14 @@
+package org.hyperskill.app.code.remote.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RunCodeRequest(
+    @SerialName("code")
+    val code: String,
+    @SerialName("language")
+    val language: String,
+    @SerialName("stdin")
+    val stdin: String
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt
new file mode 100644
index 000000000..582e34c88
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.code.remote.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
+import org.hyperskill.app.core.remote.Meta
+import org.hyperskill.app.core.remote.MetaResponse
+
+@Serializable
+data class RunCodeResponse(
+    @SerialName("meta")
+    override val meta: Meta,
+    @SerialName("run-code")
+    val codeExecutionResults: List<CodeExecutionResult>
+) : MetaResponse
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
index ea136dc42..4356c37f4 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
@@ -8,6 +8,7 @@ import org.hyperskill.app.auth.injection.AuthSocialComponent
 import org.hyperskill.app.badges.injection.BadgesDataComponent
 import org.hyperskill.app.challenges.injection.ChallengesDataComponent
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponent
+import org.hyperskill.app.code.injection.CodeDataComponent
 import org.hyperskill.app.comments.injection.CommentsDataComponent
 import org.hyperskill.app.comments.screen.domain.model.CommentsScreenFeatureParams
 import org.hyperskill.app.comments.screen.injection.CommentsScreenComponent
@@ -218,4 +219,5 @@ interface AppGraph {
     fun buildCommentsScreenComponent(
         params: CommentsScreenFeatureParams
     ): CommentsScreenComponent
+    fun buildCodeDataComponent(): CodeDataComponent
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
index 2b6a0282a..50085b798 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
@@ -14,6 +14,8 @@ import org.hyperskill.app.challenges.injection.ChallengesDataComponent
 import org.hyperskill.app.challenges.injection.ChallengesDataComponentImpl
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponent
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponentImpl
+import org.hyperskill.app.code.injection.CodeDataComponent
+import org.hyperskill.app.code.injection.CodeDataComponentImpl
 import org.hyperskill.app.comments.injection.CommentsDataComponent
 import org.hyperskill.app.comments.injection.CommentsDataComponentImpl
 import org.hyperskill.app.comments.screen.domain.model.CommentsScreenFeatureParams
@@ -556,4 +558,7 @@ abstract class BaseAppGraph : AppGraph {
         params: CommentsScreenFeatureParams
     ): CommentsScreenComponent =
         CommentsScreenComponentImpl(appGraph = this, params = params)
+
+    override fun buildCodeDataComponent(): CodeDataComponent =
+        CodeDataComponentImpl(appGraph = this)
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
index 17043a89e..9d0b5a8c8 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
@@ -27,7 +27,7 @@ data class Block(
         @SerialName("code_templates")
         val codeTemplates: Map<String, String>? = null,
         @SerialName("samples")
-        val samples: List<List<String>>? = null,
+        private val internalSamples: List<List<String>>? = null,
         @SerialName("files")
         val files: List<File>? = null,
         @SerialName("code_blanks_strings")
@@ -41,6 +41,14 @@ data class Block(
         @SerialName("code_blanks_template")
         val codeBlanksTemplate: List<CodeBlockTemplateEntry>? = null
     ) {
+        val samples: List<Sample>?
+            get() = internalSamples?.mapNotNull {
+                Sample(
+                    input = it.firstOrNull() ?: return@mapNotNull null,
+                    output = it.getOrNull(1) ?: return@mapNotNull null
+                )
+            }
+
         @Serializable
         data class File(
             @SerialName("name")
@@ -50,6 +58,11 @@ data class Block(
             @SerialName("text")
             val text: String
         )
+
+        data class Sample(
+            val input: String,
+            val output: String
+        )
     }
 }
 
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 cb7e315e6..d79794efd 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
@@ -73,6 +73,7 @@ internal class StepQuizComponentImpl(
             sentryInteractor = appGraph.sentryComponent.sentryInteractor,
             onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor,
             featuresDataSource = appGraph.profileDataComponent.featuresDataSource,
+            codeRepository = appGraph.buildCodeDataComponent().codeRepository,
             logger = appGraph.loggerComponent.logger,
             buildVariant = appGraph.commonComponent.buildKonfig.buildVariant,
             stepQuizHintsActionDispatcher = stepQuizHintsComponent.stepQuizHintsActionDispatcher,
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 5a15acdd0..46f0f8316 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
@@ -3,6 +3,7 @@ package org.hyperskill.app.step_quiz.injection
 import co.touchlab.kermit.Logger
 import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
 import org.hyperskill.app.analytic.presentation.wrapWithAnalyticLogger
+import org.hyperskill.app.code.domain.repository.CodeRepository
 import org.hyperskill.app.core.domain.BuildVariant
 import org.hyperskill.app.core.presentation.ActionDispatcherOptions
 import org.hyperskill.app.features.data.source.FeaturesDataSource
@@ -48,6 +49,7 @@ internal object StepQuizFeatureBuilder {
         sentryInteractor: SentryInteractor,
         onboardingInteractor: OnboardingInteractor,
         featuresDataSource: FeaturesDataSource,
+        codeRepository: CodeRepository,
         stepQuizHintsReducer: StepQuizHintsReducer,
         stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher,
         stepQuizToolbarReducer: StepQuizToolbarReducer,
@@ -77,6 +79,7 @@ internal object StepQuizFeatureBuilder {
             analyticInteractor = analyticInteractor,
             sentryInteractor = sentryInteractor,
             onboardingInteractor = onboardingInteractor,
+            codeRepository = codeRepository,
             logger = logger.withTag(LOG_TAG)
         )
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
index de85daa87..15ab1f3c0 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
@@ -1,10 +1,15 @@
 package org.hyperskill.app.step_quiz.presentation
 
 import co.touchlab.kermit.Logger
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
+import org.hyperskill.app.code.domain.repository.CodeRepository
+import org.hyperskill.app.code.remote.model.RunCodeRequest
 import org.hyperskill.app.core.domain.url.HyperskillUrlPath
 import org.hyperskill.app.core.presentation.ActionDispatcherOptions
 import org.hyperskill.app.features.data.source.FeaturesDataSource
@@ -16,6 +21,9 @@ import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepositor
 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.step.domain.model.BlockName
+import org.hyperskill.app.step.domain.model.Step
+import org.hyperskill.app.step.domain.model.StepContext
 import org.hyperskill.app.step_quiz.domain.analytic.StepQuizCreateSubmissionAmplitudeAnalyticEvent
 import org.hyperskill.app.step_quiz.domain.analytic.StepQuizSubmissionCreatedAmplitudeAnalyticEvent
 import org.hyperskill.app.step_quiz.domain.interactor.StepQuizInteractor
@@ -25,6 +33,8 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Action
 import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalAction
 import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.InternalMessage
 import org.hyperskill.app.step_quiz.presentation.StepQuizFeature.Message
+import org.hyperskill.app.submissions.domain.model.Reply
+import org.hyperskill.app.submissions.domain.model.Submission
 import org.hyperskill.app.submissions.domain.model.SubmissionStatus
 import org.hyperskill.app.submissions.domain.model.isWrongOrRejected
 import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor
@@ -42,6 +52,7 @@ internal class StepQuizActionDispatcher(
     private val analyticInteractor: AnalyticInteractor,
     private val sentryInteractor: SentryInteractor,
     private val onboardingInteractor: OnboardingInteractor,
+    private val codeRepository: CodeRepository,
     private val logger: Logger
 ) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {
 
@@ -129,59 +140,8 @@ internal class StepQuizActionDispatcher(
                 )
                 onNewMessage(Message.CreateSubmissionReplyValidationResult(action.step, action.reply, validationResult))
             }
-            is Action.CreateSubmission -> {
-                val reply = action.submission.reply ?: return onNewMessage(Message.CreateSubmissionNetworkError)
-
-                analyticInteractor.logEvent(
-                    StepQuizCreateSubmissionAmplitudeAnalyticEvent(
-                        stepId = action.step.id,
-                        blockName = action.step.block.name
-                    )
-                )
-
-                val sentryTransaction = HyperskillSentryTransactionBuilder.buildStepQuizCreateSubmission(
-                    blockName = action.step.block.name
-                )
-                sentryInteractor.startTransaction(sentryTransaction)
-
-                var newAttempt: Attempt? = null
-                if (action.submission.originalStatus.isWrongOrRejected &&
-                    StepQuizResolver.isNeedRecreateAttemptForNewSubmission(action.step)
-                ) {
-                    newAttempt = stepQuizInteractor
-                        .createAttempt(action.step.id)
-                        .getOrElse {
-                            sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
-                            return onNewMessage(Message.CreateSubmissionNetworkError)
-                        }
-                }
-
-                stepQuizInteractor
-                    .createSubmission(
-                        stepId = action.step.id,
-                        solvingContext = action.stepContext,
-                        attemptId = newAttempt?.id ?: action.attemptId,
-                        reply = reply
-                    )
-                    .fold(
-                        onSuccess = { newSubmission ->
-                            sentryInteractor.finishTransaction(sentryTransaction)
-                            analyticInteractor.logEvent(
-                                StepQuizSubmissionCreatedAmplitudeAnalyticEvent(
-                                    stepId = action.step.id,
-                                    blockName = action.step.block.name,
-                                    submissionStatus = newSubmission.status
-                                )
-                            )
-                            onNewMessage(Message.CreateSubmissionSuccess(newSubmission, newAttempt))
-                        },
-                        onFailure = { e ->
-                            sentryInteractor.finishTransaction(sentryTransaction)
-                            logger.e(e) { "Failed to create submission" }
-                            onNewMessage(Message.CreateSubmissionNetworkError)
-                        }
-                    )
-            }
+            is Action.CreateSubmission ->
+                handleCreateSubmission(action, ::onNewMessage)
             is Action.SaveProblemOnboardingModalShownCacheFlag -> {
                 when (action.modalType) {
                     StepQuizFeature.ProblemOnboardingModal.Parsons ->
@@ -240,6 +200,112 @@ internal class StepQuizActionDispatcher(
         }.let(onNewMessage)
     }
 
+    private suspend fun handleCreateSubmission(
+        action: Action.CreateSubmission,
+        onNewMessage: (Message) -> Unit
+    ) {
+        val reply = action.submission.reply ?: return onNewMessage(Message.CreateSubmissionNetworkError)
+
+        analyticInteractor.logEvent(
+            StepQuizCreateSubmissionAmplitudeAnalyticEvent(
+                stepId = action.step.id,
+                blockName = action.step.block.name
+            )
+        )
+
+        sentryInteractor.withTransaction(
+            transaction = HyperskillSentryTransactionBuilder.buildStepQuizCreateSubmission(
+                blockName = action.step.block.name
+            ),
+            onError = { Message.CreateSubmissionNetworkError }
+        ) {
+            coroutineScope {
+                val createSubmissionResultDeferred = async {
+                    createSubmission(
+                        submission = action.submission,
+                        step = action.step,
+                        stepContext = action.stepContext,
+                        reply = reply,
+                        attemptId = action.attemptId
+                    )
+                }
+
+                val codeExecutionDeferred = async {
+                    runCode(step = action.step, reply = reply)
+                }
+
+                val createSubmissionResult = createSubmissionResultDeferred.await()
+
+                Message.CreateSubmissionSuccess(
+                    submission = createSubmissionResult.newSubmission,
+                    newAttempt = createSubmissionResult.newAttempt,
+                    codeExecutionResult = codeExecutionDeferred.await().getOrNull()
+                )
+            }
+        }.let(::onNewMessage)
+    }
+
+    private suspend fun createSubmission(
+        submission: Submission,
+        step: Step,
+        stepContext: StepContext,
+        attemptId: Long,
+        reply: Reply
+    ): CreateSubmissionResult {
+        val newAttempt = recreateAttemptIfNeeded(submission, step)
+
+        val newSubmission = stepQuizInteractor
+            .createSubmission(
+                stepId = step.id,
+                solvingContext = stepContext,
+                attemptId = newAttempt?.id ?: attemptId,
+                reply = reply
+            )
+            .onFailure { e -> logger.e(e) { "Failed to create submission" } }
+            .getOrThrow()
+
+        analyticInteractor.logEvent(
+            StepQuizSubmissionCreatedAmplitudeAnalyticEvent(
+                stepId = step.id,
+                blockName = step.block.name,
+                submissionStatus = newSubmission.status
+            )
+        )
+
+        return CreateSubmissionResult(newSubmission, newAttempt)
+    }
+
+    private suspend fun recreateAttemptIfNeeded(submission: Submission, step: Step): Attempt? {
+        val shouldRecreateAttempt =
+            submission.originalStatus.isWrongOrRejected &&
+                StepQuizResolver.isNeedRecreateAttemptForNewSubmission(step)
+
+        return if (shouldRecreateAttempt) {
+            stepQuizInteractor
+                .createAttempt(step.id)
+                .onFailure { e -> logger.e(e) { "Failed to recreate attempt" } }
+                .getOrThrow()
+        } else {
+            null
+        }
+    }
+
+    private suspend fun runCode(
+        step: Step,
+        reply: Reply
+    ): Result<CodeExecutionResult?> =
+        if (step.block.name == BlockName.CODE && reply.code != null) {
+            codeRepository.runCode(
+                RunCodeRequest(
+                    stdin = step.block.options.samples?.firstOrNull()?.input ?: "Empty stdin",
+                    language = reply.language ?: "",
+                    code = reply.code
+                )
+            )
+        } else {
+            Result.success(null)
+        }
+
     private suspend fun getSubmissionState(
         attemptId: Long,
         stepId: Long,
@@ -275,4 +341,9 @@ internal class StepQuizActionDispatcher(
             )
         )
     }
+
+    private data class CreateSubmissionResult(
+        val newSubmission: Submission,
+        val newAttempt: Attempt?
+    )
 }
\ 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 86fb3a95a..9cb6e4f45 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
@@ -2,6 +2,7 @@ package org.hyperskill.app.step_quiz.presentation
 
 import kotlinx.serialization.Serializable
 import org.hyperskill.app.analytic.domain.model.AnalyticEvent
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.onboarding.domain.model.ProblemsOnboardingFlags
 import org.hyperskill.app.problems_limit_info.domain.model.ProblemsLimitInfoModalContext
 import org.hyperskill.app.step.domain.model.Step
@@ -37,7 +38,8 @@ object StepQuizFeature {
             val submissionState: SubmissionState,
             val isProblemsLimitReached: Boolean,
             internal val isTheoryAvailable: Boolean,
-            internal val wrongSubmissionsCount: Int = 0
+            internal val wrongSubmissionsCount: Int = 0,
+            internal val codeExecutionResult: CodeExecutionResult? = null
         ) : StepQuizState
 
         data object NetworkError : StepQuizState
@@ -77,7 +79,11 @@ object StepQuizFeature {
          * Submit submission
          */
         data class CreateSubmissionClicked(val step: Step, val reply: Reply) : Message
-        data class CreateSubmissionSuccess(val submission: Submission, val newAttempt: Attempt? = null) : Message
+        data class CreateSubmissionSuccess(
+            val submission: Submission,
+            val newAttempt: Attempt? = null,
+            val codeExecutionResult: CodeExecutionResult? = null
+        ) : Message
         data object CreateSubmissionNetworkError : Message
         data class CreateSubmissionReplyValidationResult(
             val step: Step,
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 29ee48eed..42b7e3925 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
@@ -459,7 +459,8 @@ internal class StepQuizReducer(
                     wrongSubmissionsCount = getWrongSubmissionCount(
                         currentWrongSubmissionCount = state.stepQuizState.wrongSubmissionsCount,
                         currentSubmissionStatus = submissionStatus
-                    )
+                    ),
+                    codeExecutionResult = message.codeExecutionResult
                 )
             ) to buildSet {
                 if (submissionStatus == SubmissionStatus.WRONG &&
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
index 5ddcc2789..18b8b128e 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
@@ -1,6 +1,7 @@
 package org.hyperskill.app.step_quiz.view.mapper
 
 import org.hyperskill.app.SharedResources
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.core.view.mapper.ResourceProvider
 import org.hyperskill.app.step.domain.model.BlockName
 import org.hyperskill.app.step.domain.model.Step
@@ -43,10 +44,15 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
         return when (submission?.status) {
             SubmissionStatus.CORRECT ->
                 StepQuizFeedbackState.Correct(
-                    hint = getHint(submission),
-                    useLatex = shouldUseLatex(stepQuizState.step)
+                    hint = getHint(stepQuizState.step, submission, stepQuizState.codeExecutionResult)
                 )
-            SubmissionStatus.EVALUATION -> StepQuizFeedbackState.Evaluation
+            SubmissionStatus.EVALUATION -> StepQuizFeedbackState.Evaluation(
+                if (isCodeExecutionLaunched(stepQuizState.step)) {
+                    StepQuizFeedbackState.Hint.FromCodeExecution.Loading
+                } else {
+                    null
+                }
+            )
             SubmissionStatus.WRONG -> getWrongStatusFeedback(state, stepQuizState, submission)
             SubmissionStatus.REJECTED -> {
                 val feedback = submission.feedback
@@ -87,14 +93,10 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
                 null -> null
             },
             actionType = wrongSubmissionAction,
-            feedbackHint = getHint(submission),
-            useFeedbackHintLatex = shouldUseLatex(stepQuizState.step)
+            hint = getHint(stepQuizState.step, submission, stepQuizState.codeExecutionResult)
         )
     }
 
-    private fun shouldUseLatex(step: Step): Boolean =
-        step.block.name == BlockName.MATH
-
     private fun getWrongSubmissionAction(
         state: StepQuizFeature.State,
         stepQuizState: StepQuizFeature.StepQuizState.AttemptLoaded
@@ -113,9 +115,48 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
             else -> Action.SKIP_PROBLEM
         }
 
-    private fun getHint(submission: Submission): String? =
-        submission
-            .hint
-            ?.takeIf(String::isNotEmpty)
-            ?.replace("\n", "<br />")
+    private fun getHint(
+        step: Step,
+        submission: Submission,
+        codeExecutionResult: CodeExecutionResult?
+    ): StepQuizFeedbackState.Hint? {
+        val shouldUseCodeExecutionHint =
+            submission.status == SubmissionStatus.CORRECT &&
+                isCodeExecutionLaunched(step) && codeExecutionResult != null
+        return if (shouldUseCodeExecutionHint) {
+            getCodeExecutionFeedback(codeExecutionResult)
+        } else {
+            val text = submission
+                .hint
+                ?.takeIf(String::isNotEmpty)
+                ?.replace("\n", "<br />")
+            text?.let {
+                StepQuizFeedbackState.Hint.FromSubmission(
+                    text = text,
+                    useLatex = step.block.name == BlockName.MATH
+                )
+            }
+        }
+    }
+
+    private fun isCodeExecutionLaunched(step: Step): Boolean =
+        step.block.name == BlockName.CODE
+
+    private fun getCodeExecutionFeedback(
+        codeExecutionResult: CodeExecutionResult?
+    ): StepQuizFeedbackState.Hint.FromCodeExecution.Result? =
+        when {
+            codeExecutionResult != null -> {
+                val stdout = codeExecutionResult.stdout
+                if (!stdout.isNullOrEmpty()) {
+                    StepQuizFeedbackState.Hint.FromCodeExecution.Result(
+                        input = codeExecutionResult.stdin?.takeIf { it.isNotEmpty() }?.trim(),
+                        output = codeExecutionResult.stdout.trim()
+                    )
+                } else {
+                    null
+                }
+            }
+            else -> null
+        }
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
index 4a1e57886..4709b9299 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
@@ -3,18 +3,14 @@ package org.hyperskill.app.step_quiz.view.model
 sealed interface StepQuizFeedbackState {
     data object Idle : StepQuizFeedbackState
 
-    data class Correct(
-        val hint: String?,
-        val useLatex: Boolean
-    ) : StepQuizFeedbackState
+    data class Correct(val hint: Hint?) : StepQuizFeedbackState
 
     data class Wrong(
         val title: String,
         val description: String?,
         val actionText: String?,
         val actionType: Action?,
-        val feedbackHint: String?,
-        val useFeedbackHintLatex: Boolean,
+        val hint: Hint?
     ) : StepQuizFeedbackState {
         enum class Action {
             SEE_HINT,
@@ -25,9 +21,24 @@ sealed interface StepQuizFeedbackState {
 
     data class RejectedSubmission(val message: String) : StepQuizFeedbackState
 
-    data object Evaluation : StepQuizFeedbackState
+    data class Evaluation(val hint: Hint.FromCodeExecution?) : StepQuizFeedbackState
 
     data class ValidationFailed(val message: String) : StepQuizFeedbackState
 
     data object UnsupportedStep : StepQuizFeedbackState
+
+    sealed interface Hint {
+        data class FromSubmission(
+            val text: String,
+            val useLatex: Boolean
+        ) : Hint
+
+        sealed interface FromCodeExecution : Hint {
+            data object Loading : FromCodeExecution
+            data class Result(
+                val input: String?,
+                val output: String
+            ) : FromCodeExecution
+        }
+    }
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml
index d6fdb269b..3952b69cb 100644
--- a/shared/src/commonMain/moko-resources/base/strings.xml
+++ b/shared/src/commonMain/moko-resources/base/strings.xml
@@ -172,6 +172,9 @@
     <string name="step_quiz_code_reset">Reset</string>
     <string name="step_quiz_code_editor_title">Code editor</string>
     <string name="step_quiz_code_language_template">(%s)</string>
+    <string name="step_quiz_code_execution_title">Running test cases</string>
+    <string name="step_quiz_code_execution_input">Program input:</string>
+    <string name="step_quiz_code_execution_output">Program output:</string>
 
     <!-- Step quiz sql -->
     <string name="step_quiz_sql_title">Write an SQL statement</string>
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
index 19667bd6c..80f5cddfa 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
@@ -4,9 +4,12 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertIs
 import org.hyperskill.ResourceProviderStub
+import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.comments.domain.model.Comment
 import org.hyperskill.app.comments.domain.model.CommentStatisticsEntry
 import org.hyperskill.app.comments.domain.model.CommentThread
+import org.hyperskill.app.step.domain.model.Block
+import org.hyperskill.app.step.domain.model.BlockName
 import org.hyperskill.app.step.domain.model.Step
 import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt
 import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult
@@ -87,7 +90,15 @@ class StepQuizFeedbackMapperTest {
     fun `SubmissionStatus_CORRECT should be mapped to Correct viewState`() {
         val hint = "Test hint"
         val state = getAttemptLoadedSubmissionState(
-            Submission.stub(
+            step = Step.stub(
+                id = 0,
+                block = Block(
+                    name = BlockName.TEXT,
+                    text = "",
+                    options = Block.Options()
+                )
+            ),
+            submission = Submission.stub(
                 status = SubmissionStatus.CORRECT,
                 hint = hint
             )
@@ -95,8 +106,49 @@ class StepQuizFeedbackMapperTest {
 
         val viewState = mapper.map(state)
 
+        val expectedHint = StepQuizFeedbackState.Hint.FromSubmission(
+            text = hint,
+            useLatex = false
+        )
+
+        assertIs<StepQuizFeedbackState.Correct>(viewState)
+        assertEquals(expectedHint, viewState.hint)
+    }
+
+    @Test
+    fun `SubmissionStatus_Correct should be mapped to Correct view state with code execution hint for code step`() {
+        val stdout = "test out"
+        val stdin = "test in"
+        val state = getAttemptLoadedSubmissionState(
+            step = Step.stub(
+                id = 0,
+                block = Block(
+                    name = BlockName.CODE,
+                    text = "",
+                    options = Block.Options()
+                )
+            ),
+            submission = Submission.stub(
+                status = SubmissionStatus.CORRECT,
+                hint = null
+            ),
+            codeExecutionResult = CodeExecutionResult(
+                language = "kotlin",
+                stdin = stdin,
+                stdout = stdout,
+                isSuccess = true
+            )
+        )
+
+        val viewState = mapper.map(state)
+
+        val expectedHint = StepQuizFeedbackState.Hint.FromCodeExecution.Result(
+            input = stdin,
+            output = stdout
+        )
+
         assertIs<StepQuizFeedbackState.Correct>(viewState)
-        assertEquals(hint, viewState.hint)
+        assertEquals(expectedHint, viewState.hint)
     }
 
     @Test
@@ -270,7 +322,8 @@ class StepQuizFeedbackMapperTest {
         replyValidation: ReplyValidationResult? = null,
         wrongSubmissionCount: Int = 0,
         step: Step = Step.stub(id = 0),
-        stepQuizHintsState: StepQuizHintsFeature.State = StepQuizHintsFeature.State.Idle
+        stepQuizHintsState: StepQuizHintsFeature.State = StepQuizHintsFeature.State.Idle,
+        codeExecutionResult: CodeExecutionResult? = null
     ): StepQuizFeature.State =
         StepQuizFeature.State(
             stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded(
@@ -282,7 +335,8 @@ class StepQuizFeedbackMapperTest {
                 ),
                 isProblemsLimitReached = false,
                 isTheoryAvailable = false,
-                wrongSubmissionsCount = wrongSubmissionCount
+                wrongSubmissionsCount = wrongSubmissionCount,
+                codeExecutionResult = codeExecutionResult
             ),
             stepQuizHintsState = stepQuizHintsState,
             stepQuizToolbarState = StepQuizToolbarFeature.State.Idle,

From 7e57932f181137d085ae09cddf85a3b6bb4c0dc4 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 16 Sep 2024 10:33:44 +0000
Subject: [PATCH 04/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 6f32ffb48..1352c97e5 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '548'
\ No newline at end of file
+versionCode = '549'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index d269857f3..de88a30bf 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>577</string>
+	<string>578</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 58e862c40..34b214a33 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 577;
+				CURRENT_PROJECT_VERSION = 578;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 730498d2b..110d69251 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>577</string>
+	<string>578</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index f046c6eda..ab090c631 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>577</string>
+	<string>578</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index cd2d438df..43a40f808 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>577</string>
+	<string>578</string>
 </dict>
 </plist>

From c49c6f6eb6f3e8bb2ad1e32edfbc51ecc4a498e4 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Tue, 17 Sep 2024 16:21:41 +0900
Subject: [PATCH 05/27] Shared: Python adopted course selection (#1181)

^ALTAPPS-1356
---
 .../app/profile/domain/model/FeatureKeys.kt       |  1 +
 .../app/profile/domain/model/FeaturesMap.kt       |  5 ++++-
 .../root/presentation/WelcomeOnboardingReducer.kt |  4 +++-
 .../WelcomeOnboardingTrackDetailsComponentImpl.kt |  4 +++-
 ...WelcomeOnboardingTrackDetailsFeatureBuilder.kt | 15 +++++++++++++--
 .../WelcomeOnboardingTrackDetailsFeature.kt       | 11 ++++++++---
 .../WelcomeOnboardingTrackDetailsReducer.kt       | 10 ++++++----
 7 files changed, 38 insertions(+), 12 deletions(-)

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 b12ff2408..61f09905b 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
@@ -11,4 +11,5 @@ internal object FeatureKeys {
     const val MOBILE_ONLY_SUBSCRIPTION = "mobile.mobile_only_subscription"
     const val MOBILE_USERS_INTERVIEW_WIDGET = "mobile.users_interview_widget"
     const val MOBILE_CONTENT_TRIAL = "mobile.content_trial"
+    const val MOBILE_PYTHON_ADOPTED_COURSE = "mobile.python_adopted_course"
 }
\ 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 200905886..e06709f59 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
@@ -39,4 +39,7 @@ val FeaturesMap.isMobileUsersInterviewWidgetEnabled: Boolean
     get() = get(FeatureKeys.MOBILE_USERS_INTERVIEW_WIDGET) ?: false
 
 val FeaturesMap.isMobileContentTrialEnabled: Boolean
-    get() = get(FeatureKeys.MOBILE_CONTENT_TRIAL) ?: false
\ No newline at end of file
+    get() = get(FeatureKeys.MOBILE_CONTENT_TRIAL) ?: false
+
+val FeaturesMap.isMobilePythonAdoptedCourseEnabled: Boolean
+    get() = get(FeatureKeys.MOBILE_PYTHON_ADOPTED_COURSE) ?: false
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/root/presentation/WelcomeOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/root/presentation/WelcomeOnboardingReducer.kt
index 2fe4aadbd..ecd772243 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/root/presentation/WelcomeOnboardingReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/root/presentation/WelcomeOnboardingReducer.kt
@@ -83,7 +83,9 @@ internal class WelcomeOnboardingReducer : StateReducer<State, Message, Action> {
                 WelcomeQuestionnaireType.LEARNING_REASON ->
                     NavigateTo.WelcomeOnboardingQuestionnaire(WelcomeQuestionnaireType.CODING_EXPERIENCE)
                 WelcomeQuestionnaireType.CODING_EXPERIENCE ->
-                    NavigateTo.ChooseProgrammingLanguage
+                    // NavigateTo.ChooseProgrammingLanguage
+                    // ALTAPPS-1356: Redirect to Python track directly
+                    NavigateTo.TrackDetails(WelcomeOnboardingTrack.PYTHON)
             }
         )
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsComponentImpl.kt
index a42c5d504..2f451d7ef 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsComponentImpl.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsComponentImpl.kt
@@ -17,8 +17,10 @@ internal class WelcomeOnboardingTrackDetailsComponentImpl(
             currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository,
             profileRepository = appGraph.profileDataComponent.profileRepository,
             analyticInteractor = appGraph.analyticComponent.analyticInteractor,
+            featuresDataSource = appGraph.profileDataComponent.featuresDataSource,
             logger = appGraph.loggerComponent.logger,
             buildVariant = appGraph.commonComponent.buildKonfig.buildVariant,
-            resourceProvider = appGraph.commonComponent.resourceProvider
+            resourceProvider = appGraph.commonComponent.resourceProvider,
+            platformType = appGraph.commonComponent.platform.platformType
         )
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsFeatureBuilder.kt
index 0ec5712f3..334f8bf3c 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsFeatureBuilder.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/injection/WelcomeOnboardingTrackDetailsFeatureBuilder.kt
@@ -4,10 +4,13 @@ import co.touchlab.kermit.Logger
 import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
 import org.hyperskill.app.analytic.presentation.wrapWithAnalyticLogger
 import org.hyperskill.app.core.domain.BuildVariant
+import org.hyperskill.app.core.domain.platform.PlatformType
 import org.hyperskill.app.core.presentation.ActionDispatcherOptions
 import org.hyperskill.app.core.presentation.transformState
 import org.hyperskill.app.core.view.mapper.ResourceProvider
+import org.hyperskill.app.features.data.source.FeaturesDataSource
 import org.hyperskill.app.logging.presentation.wrapWithLogger
+import org.hyperskill.app.profile.domain.model.isMobilePythonAdoptedCourseEnabled
 import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
 import org.hyperskill.app.profile.domain.repository.ProfileRepository
 import org.hyperskill.app.welcome_onboarding.model.WelcomeOnboardingTrack
@@ -31,10 +34,15 @@ internal object WelcomeOnboardingTrackDetailsFeatureBuilder {
         currentProfileStateRepository: CurrentProfileStateRepository,
         profileRepository: ProfileRepository,
         analyticInteractor: AnalyticInteractor,
+        featuresDataSource: FeaturesDataSource,
         resourceProvider: ResourceProvider,
         logger: Logger,
-        buildVariant: BuildVariant
+        buildVariant: BuildVariant,
+        platformType: PlatformType
     ): Feature<ViewState, Message, Action> {
+        val isMobilePythonAdoptedCourseEnabled = platformType == PlatformType.IOS &&
+            featuresDataSource.getFeaturesMap().isMobilePythonAdoptedCourseEnabled
+
         val welcomeOnboardingTrackDetailsReducer =
             WelcomeOnboardingTrackDetailsReducer()
                 .wrapWithLogger(buildVariant, logger, LOG_TAG)
@@ -49,7 +57,10 @@ internal object WelcomeOnboardingTrackDetailsFeatureBuilder {
         val viewStateMapper = WelcomeOnboardingTrackDetailsViewStateMapper(resourceProvider)
 
         return ReduxFeature(
-            initialState = WelcomeOnboardingTrackDetailsFeature.initialState(track),
+            initialState = WelcomeOnboardingTrackDetailsFeature.initialState(
+                track = track,
+                isMobilePythonAdoptedCourseEnabled = isMobilePythonAdoptedCourseEnabled
+            ),
             reducer = welcomeOnboardingTrackDetailsReducer
         )
             .wrapWithActionDispatcher(welcomeOnboardingTrackDetailsActionDispatcher)
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsFeature.kt
index 6b58a3e51..f6d7d3d47 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsFeature.kt
@@ -6,13 +6,18 @@ import org.hyperskill.app.welcome_onboarding.model.WelcomeOnboardingTrack
 object WelcomeOnboardingTrackDetailsFeature {
     internal data class State(
         val track: WelcomeOnboardingTrack,
-        val isLoadingShowed: Boolean
+        val isLoadingShowed: Boolean,
+        val isMobilePythonAdoptedCourseEnabled: Boolean
     )
 
-    internal fun initialState(track: WelcomeOnboardingTrack) =
+    internal fun initialState(
+        track: WelcomeOnboardingTrack,
+        isMobilePythonAdoptedCourseEnabled: Boolean
+    ) =
         State(
             track = track,
-            isLoadingShowed = false
+            isLoadingShowed = false,
+            isMobilePythonAdoptedCourseEnabled = isMobilePythonAdoptedCourseEnabled
         )
 
     /**
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsReducer.kt
index 9355802a5..d069c81e2 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/welcome_onboarding/track_details/presentation/WelcomeOnboardingTrackDetailsReducer.kt
@@ -20,6 +20,7 @@ internal class WelcomeOnboardingTrackDetailsReducer : StateReducer<State, Messag
         private const val JS_TRACK_ID = 32L
         private const val KOTLIN_TRACK_ID = 69L
         private const val PYTHON_TRACK_ID = 6L
+        private const val PYTHON_TRACK_MOBILE_ADOPTED_ID = 139L
         private const val SQL_TRACK_ID = 31L
     }
 
@@ -41,7 +42,7 @@ internal class WelcomeOnboardingTrackDetailsReducer : StateReducer<State, Messag
             InternalAction.LogAnalyticEvent(
                 WelcomeOnboardingSelectTrackClickedHSAnalyticEvent(state.track)
             ),
-            InternalAction.SelectTrack(getTrackId(state.track))
+            InternalAction.SelectTrack(getTrackId(state))
         )
 
     private fun handleSelectTrackSuccess(state: State): WelcomeOnboardingTrackDetailsReducerResult =
@@ -50,12 +51,13 @@ internal class WelcomeOnboardingTrackDetailsReducer : StateReducer<State, Messag
     private fun handleSelectTrackFailed(state: State): WelcomeOnboardingTrackDetailsReducerResult =
         state.copy(isLoadingShowed = false) to setOf(Action.ViewAction.ShowTrackSelectionError)
 
-    private fun getTrackId(track: WelcomeOnboardingTrack): Long =
-        when (track) {
+    private fun getTrackId(state: State): Long =
+        when (state.track) {
             WelcomeOnboardingTrack.JAVA -> JAVA_TRACK_ID
             WelcomeOnboardingTrack.JAVA_SCRIPT -> JS_TRACK_ID
             WelcomeOnboardingTrack.KOTLIN -> KOTLIN_TRACK_ID
-            WelcomeOnboardingTrack.PYTHON -> PYTHON_TRACK_ID
+            WelcomeOnboardingTrack.PYTHON ->
+                if (state.isMobilePythonAdoptedCourseEnabled) PYTHON_TRACK_MOBILE_ADOPTED_ID else PYTHON_TRACK_ID
             WelcomeOnboardingTrack.SQL -> SQL_TRACK_ID
         }
 }
\ No newline at end of file

From 85a2a685375fa317a9b81a8f135d5d2a88bd8cbf Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Tue, 17 Sep 2024 07:22:16 +0000
Subject: [PATCH 06/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 1352c97e5..1e0922514 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '549'
\ No newline at end of file
+versionCode = '550'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index de88a30bf..d21b1b795 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>578</string>
+	<string>579</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 34b214a33..f2ae61048 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 578;
+				CURRENT_PROJECT_VERSION = 579;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 110d69251..39255604d 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>578</string>
+	<string>579</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index ab090c631..518d4f6da 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>578</string>
+	<string>579</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 43a40f808..7d4e1781f 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>578</string>
+	<string>579</string>
 </dict>
 </plist>

From 1123c3a56253f8506aa751b60620515232e8f09d Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Wed, 18 Sep 2024 15:14:59 +0900
Subject: [PATCH 07/27] Shared, iOS: Code blanks mechanics onboarding (#1183)

^ALTAPPS-1357
---
 config/detekt/baseline.xml                    |  2 -
 .../StepQuizCodeBlanksActionButton.swift      | 28 +++++--
 .../StepQuizCodeBlanksActionButtonsView.swift | 17 ++++-
 .../StepQuizCodeBlanksCodeBlocksView.swift    |  2 +
 .../step_quiz/AndroidStepQuizTest.kt          |  5 +-
 .../StepQuizCodeBlanksComponentImpl.kt        |  3 +-
 .../presentation/StepQuizCodeBlanksFeature.kt | 10 ++-
 .../StepQuizCodeBlanksOnboardingReducer.kt    | 48 ++++++++++++
 .../presentation/StepQuizCodeBlanksReducer.kt | 31 +++-----
 .../StepQuizCodeBlanksResolver.kt             |  5 --
 .../view/model/StepQuizCodeBlanksViewState.kt | 15 +++-
 .../StepQuizChildFeatureReducerStub.kt        |  3 +-
 ...eBlanksReducerCodeBlockChildClickedTest.kt |  3 +-
 ...izCodeBlanksReducerCodeBlockClickedTest.kt |  3 +-
 ...cerDecreaseIndentLevelButtonClickedTest.kt |  3 +-
 ...odeBlanksReducerDeleteButtonClickedTest.kt |  3 +-
 ...CodeBlanksReducerEnterButtonClickedTest.kt |  3 +-
 ...StepQuizCodeBlanksReducerInitializeTest.kt |  3 +-
 ...StepQuizCodeBlanksReducerOnboardingTest.kt | 39 ++++++++--
 ...CodeBlanksReducerSpaceButtonClickedTest.kt |  3 +-
 .../StepQuizCodeBlanksReducerStub.kt          | 10 +++
 ...zCodeBlanksReducerSuggestionClickedTest.kt |  3 +-
 .../view/StepQuizCodeBlanksViewStateTest.kt   | 75 ++++++++++++++++---
 23 files changed, 240 insertions(+), 77 deletions(-)
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
 create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerStub.kt

diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index 578761281..b3066984b 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -25,9 +25,7 @@
     <ID>CyclomaticComplexMethod:LeaderboardWidgetReducer.kt$LeaderboardWidgetReducer$override fun reduce(state: State, message: Message): LeaderboardWidgetReducerResult</ID>
     <ID>CyclomaticComplexMethod:LoadingView.kt$LoadingView$override fun onDraw(canvas: Canvas)</ID>
     <ID>CyclomaticComplexMethod:ProfileReducer.kt$ProfileReducer$override fun reduce(state: State, message: Message): ReducerResult</ID>
-    <ID>CyclomaticComplexMethod:StepQuizActionDispatcher.kt$StepQuizActionDispatcher$override suspend fun doSuspendableAction(action: Action)</ID>
     <ID>CyclomaticComplexMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleDeleteButtonClicked( state: State ): StepQuizCodeBlanksReducerResult?</ID>
-    <ID>CyclomaticComplexMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSuggestionClicked( state: State, message: Message.SuggestionClicked ): StepQuizCodeBlanksReducerResult?</ID>
     <ID>CyclomaticComplexMethod:StepQuizCodeBlanksViewStateMapper.kt$StepQuizCodeBlanksViewStateMapper$private fun mapContentState( state: StepQuizCodeBlanksFeature.State.Content ): StepQuizCodeBlanksViewState.Content</ID>
     <ID>CyclomaticComplexMethod:StepQuizHintsReducer.kt$StepQuizHintsReducer$override fun reduce(state: State, message: Message): StepQuizHintsReducerResult</ID>
     <ID>CyclomaticComplexMethod:StepQuizReducer.kt$StepQuizReducer$override fun reduce(state: State, message: Message): StepQuizReducerResult</ID>
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
index 00744ba2e..54ae95110 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
@@ -16,6 +16,8 @@ struct StepQuizCodeBlanksActionButton: View {
 
     let imageSystemName: String
 
+    var isAnimationEffectActive = false
+
     let action: () -> Void
 
     @Environment(\.isEnabled) private var isEnabled
@@ -33,6 +35,11 @@ struct StepQuizCodeBlanksActionButton: View {
                     )
                     .foregroundColor(Color(ColorPalette.onPrimary))
                     .cornerRadius(appearance.cornerRadius)
+                    .shineEffect(isActive: isEnabled && isAnimationEffectActive)
+                    .pulseEffect(
+                        shape: RoundedRectangle(cornerRadius: appearance.cornerRadius),
+                        isActive: isEnabled && isAnimationEffectActive
+                    )
             }
         )
         .buttonStyle(BounceButtonStyle())
@@ -40,8 +47,15 @@ struct StepQuizCodeBlanksActionButton: View {
 }
 
 extension StepQuizCodeBlanksActionButton {
-    static func delete(action: @escaping () -> Void) -> StepQuizCodeBlanksActionButton {
-        StepQuizCodeBlanksActionButton(imageSystemName: "delete.left", action: action)
+    static func delete(
+        isAnimationEffectActive: Bool,
+        action: @escaping () -> Void
+    ) -> StepQuizCodeBlanksActionButton {
+        StepQuizCodeBlanksActionButton(
+            imageSystemName: "delete.left",
+            isAnimationEffectActive: isAnimationEffectActive,
+            action: action
+        )
     }
 
     static func enter(action: @escaping () -> Void) -> StepQuizCodeBlanksActionButton {
@@ -49,6 +63,7 @@ extension StepQuizCodeBlanksActionButton {
     }
 
     static func space(
+        isAnimationEffectActive: Bool,
         action: @escaping () -> Void
     ) -> StepQuizCodeBlanksActionButton {
         StepQuizCodeBlanksActionButton(
@@ -59,6 +74,7 @@ extension StepQuizCodeBlanksActionButton {
                 )
             ),
             imageSystemName: "space",
+            isAnimationEffectActive: isAnimationEffectActive,
             action: action
         )
     }
@@ -81,16 +97,16 @@ extension StepQuizCodeBlanksActionButton {
 #Preview {
     VStack {
         HStack {
-            StepQuizCodeBlanksActionButton.delete(action: {})
+            StepQuizCodeBlanksActionButton.delete(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.enter(action: {})
-            StepQuizCodeBlanksActionButton.space(action: {})
+            StepQuizCodeBlanksActionButton.space(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {})
         }
 
         HStack {
-            StepQuizCodeBlanksActionButton.delete(action: {})
+            StepQuizCodeBlanksActionButton.delete(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.enter(action: {})
-            StepQuizCodeBlanksActionButton.space(action: {})
+            StepQuizCodeBlanksActionButton.space(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {})
         }
         .disabled(true)
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
index f6a041701..f06b0d607 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
@@ -5,6 +5,9 @@ struct StepQuizCodeBlanksActionButtonsView: View {
     let isSpaceButtonHidden: Bool
     let isDecreaseIndentLevelButtonHidden: Bool
 
+    let isDeleteButtonHighlightEffectActive: Bool
+    let isSpaceButtonHighlightEffectActive: Bool
+
     let onSpaceTap: () -> Void
     let onDeleteTap: () -> Void
     let onEnterTap: () -> Void
@@ -21,11 +24,17 @@ struct StepQuizCodeBlanksActionButtonsView: View {
 
             if !isSpaceButtonHidden {
                 StepQuizCodeBlanksActionButton
-                    .space(action: onSpaceTap)
+                    .space(
+                        isAnimationEffectActive: isSpaceButtonHighlightEffectActive,
+                        action: onSpaceTap
+                    )
             }
 
             StepQuizCodeBlanksActionButton
-                .delete(action: onDeleteTap)
+                .delete(
+                    isAnimationEffectActive: isDeleteButtonHighlightEffectActive,
+                    action: onDeleteTap
+                )
                 .disabled(!isDeleteButtonEnabled)
 
             StepQuizCodeBlanksActionButton
@@ -42,6 +51,8 @@ struct StepQuizCodeBlanksActionButtonsView: View {
             isDeleteButtonEnabled: false,
             isSpaceButtonHidden: false,
             isDecreaseIndentLevelButtonHidden: false,
+            isDeleteButtonHighlightEffectActive: false,
+            isSpaceButtonHighlightEffectActive: true,
             onSpaceTap: {},
             onDeleteTap: {},
             onEnterTap: {},
@@ -52,6 +63,8 @@ struct StepQuizCodeBlanksActionButtonsView: View {
             isDeleteButtonEnabled: true,
             isSpaceButtonHidden: true,
             isDecreaseIndentLevelButtonHidden: true,
+            isDeleteButtonHighlightEffectActive: true,
+            isSpaceButtonHighlightEffectActive: false,
             onSpaceTap: {},
             onDeleteTap: {},
             onEnterTap: {},
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
index 9b5e4c634..acb66cf80 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
@@ -39,6 +39,8 @@ struct StepQuizCodeBlanksCodeBlocksView: View {
                     isDeleteButtonEnabled: state.isDeleteButtonEnabled,
                     isSpaceButtonHidden: state.isSpaceButtonHidden,
                     isDecreaseIndentLevelButtonHidden: state.isDecreaseIndentLevelButtonHidden,
+                    isDeleteButtonHighlightEffectActive: state.isDeleteButtonHighlightEffectActive,
+                    isSpaceButtonHighlightEffectActive: state.isSpaceButtonHighlightEffectActive,
                     onSpaceTap: onSpaceTap,
                     onDeleteTap: onDeleteTap,
                     onEnterTap: onEnterTap,
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 be32ef6b5..d3649ba04 100644
--- a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt
+++ b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt
@@ -20,6 +20,7 @@ import org.hyperskill.app.subscriptions.domain.model.SubscriptionType
 import org.hyperskill.onboarding.domain.model.stub
 import org.hyperskill.step.domain.model.stub
 import org.hyperskill.step_quiz.domain.model.stub
+import org.hyperskill.step_quiz_code_blanks.presentation.stub
 import org.hyperskill.subscriptions.stub
 import org.junit.Test
 
@@ -78,8 +79,8 @@ class AndroidStepQuizTest {
                     stepQuizChildFeatureReducer = StepQuizChildFeatureReducer(
                         stepQuizHintsReducer = StepQuizHintsReducer(stepRoute),
                         stepQuizToolbarReducer = StepQuizToolbarReducer(stepRoute),
-                        stepQuizCodeBlanksReducer = StepQuizCodeBlanksReducer(stepRoute)
-                    ),
+                        stepQuizCodeBlanksReducer = StepQuizCodeBlanksReducer.stub(stepRoute)
+                    )
                 )
 
                 val (state, _) = reducer.reduce(
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/injection/StepQuizCodeBlanksComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/injection/StepQuizCodeBlanksComponentImpl.kt
index 2e2a5be64..eb687eff6 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/injection/StepQuizCodeBlanksComponentImpl.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/injection/StepQuizCodeBlanksComponentImpl.kt
@@ -4,6 +4,7 @@ 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_code_blanks.presentation.StepQuizCodeBlanksActionDispatcher
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksOnboardingReducer
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 
 internal class StepQuizCodeBlanksComponentImpl(
@@ -11,7 +12,7 @@ internal class StepQuizCodeBlanksComponentImpl(
     private val stepRoute: StepRoute
 ) : StepQuizCodeBlanksComponent {
     override val stepQuizCodeBlanksReducer: StepQuizCodeBlanksReducer
-        get() = StepQuizCodeBlanksReducer(stepRoute)
+        get() = StepQuizCodeBlanksReducer(stepRoute, StepQuizCodeBlanksOnboardingReducer())
 
     override val stepQuizCodeBlanksActionDispatcher: StepQuizCodeBlanksActionDispatcher
         get() = StepQuizCodeBlanksActionDispatcher(
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
index 6e75accad..667193697 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
@@ -39,8 +39,14 @@ object StepQuizCodeBlanksFeature {
 
     sealed interface OnboardingState {
         data object Unavailable : OnboardingState
-        data object HighlightSuggestions : OnboardingState
-        data object HighlightCallToActionButton : OnboardingState
+
+        data object HighlightDeleteButton : OnboardingState
+        data object HighlightSpaceButton : OnboardingState
+
+        sealed interface PrintSuggestionAndCallToAction : OnboardingState {
+            data object HighlightSuggestions : PrintSuggestionAndCallToAction
+            data object HighlightCallToActionButton : PrintSuggestionAndCallToAction
+        }
     }
 
     sealed interface Message {
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
new file mode 100644
index 000000000..46e84e782
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
@@ -0,0 +1,48 @@
+package org.hyperskill.app.step_quiz_code_blanks.presentation
+
+import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.Action
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalAction
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalMessage
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.State
+
+class StepQuizCodeBlanksOnboardingReducer {
+    companion object {
+        private const val DELETE_BUTTON_STEP_ID = 50969L
+        private const val SPACE_BUTTON_STEP_ID = 50970L
+        private val PRINT_SUGGESTION_AND_CALL_TO_ACTION_STEP_IDS = setOf(47329L, 50968L)
+    }
+
+    internal fun reduceInitializeMessage(
+        message: InternalMessage.Initialize
+    ): OnboardingState =
+        when (message.step.id) {
+            in PRINT_SUGGESTION_AND_CALL_TO_ACTION_STEP_IDS ->
+                OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+            DELETE_BUTTON_STEP_ID -> OnboardingState.HighlightDeleteButton
+            SPACE_BUTTON_STEP_ID -> OnboardingState.HighlightSpaceButton
+            else -> OnboardingState.Unavailable
+        }
+
+    internal fun reduceSuggestionClickedMessage(
+        state: State.Content,
+        activeCodeBlock: CodeBlock?,
+        newCodeBlock: CodeBlock
+    ): Pair<OnboardingState, Set<Action>> {
+        val isFulfilledOnboardingPrintCodeBlock =
+            state.onboardingState is OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions &&
+                activeCodeBlock is CodeBlock.Print && activeCodeBlock.hasAnyUnselectedChild() &&
+                newCodeBlock is CodeBlock.Print && newCodeBlock.areAllChildrenSelected()
+        return if (isFulfilledOnboardingPrintCodeBlock) {
+            OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton to
+                setOf(
+                    InternalAction.ParentFeatureActionRequested(
+                        StepQuizCodeBlanksFeature.ParentFeatureAction.HighlightCallToActionButton
+                    )
+                )
+        } else {
+            state.onboardingState to emptySet()
+        }
+    }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
index afb5196da..90eb004db 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
@@ -20,7 +20,6 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalAction
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalMessage
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.Message
-import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.State
 import ru.nobird.app.core.model.mutate
 import ru.nobird.app.presentation.redux.reducer.StateReducer
@@ -28,8 +27,11 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer
 private typealias StepQuizCodeBlanksReducerResult = Pair<State, Set<Action>>
 
 class StepQuizCodeBlanksReducer(
-    private val stepRoute: StepRoute
+    private val stepRoute: StepRoute,
+    private val stepQuizCodeBlanksOnboardingReducer: StepQuizCodeBlanksOnboardingReducer
 ) : StateReducer<State, Message, Action> {
+    companion object;
+
     override fun reduce(state: State, message: Message): StepQuizCodeBlanksReducerResult =
         when (message) {
             is InternalMessage.Initialize -> initialize(message)
@@ -48,11 +50,7 @@ class StepQuizCodeBlanksReducer(
         State.Content(
             step = message.step,
             codeBlocks = createInitialCodeBlocks(step = message.step),
-            onboardingState = if (StepQuizCodeBlanksResolver.isOnboardingAvailable(message.step)) {
-                OnboardingState.HighlightSuggestions
-            } else {
-                OnboardingState.Unavailable
-            }
+            onboardingState = stepQuizCodeBlanksOnboardingReducer.reduceInitializeMessage(message)
         ) to emptySet()
 
     private fun handleSuggestionClicked(
@@ -209,21 +207,12 @@ class StepQuizCodeBlanksReducer(
             }
         }
 
-        val isFulfilledOnboardingPrintCodeBlock =
-            state.onboardingState is OnboardingState.HighlightSuggestions &&
-                activeCodeBlock is CodeBlock.Print && activeCodeBlock.hasAnyUnselectedChild() &&
-                newCodeBlock is CodeBlock.Print && newCodeBlock.areAllChildrenSelected()
         val (onboardingState, onboardingActions) =
-            if (isFulfilledOnboardingPrintCodeBlock) {
-                OnboardingState.HighlightCallToActionButton to
-                    setOf(
-                        InternalAction.ParentFeatureActionRequested(
-                            StepQuizCodeBlanksFeature.ParentFeatureAction.HighlightCallToActionButton
-                        )
-                    )
-            } else {
-                state.onboardingState to emptySet()
-            }
+            stepQuizCodeBlanksOnboardingReducer.reduceSuggestionClickedMessage(
+                state = state,
+                activeCodeBlock = activeCodeBlock,
+                newCodeBlock = newCodeBlock
+            )
 
         return state.copy(
             codeBlocks = newCodeBlocks,
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
index 6e5e877c2..670cb4732 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
@@ -6,13 +6,8 @@ import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
 import ru.nobird.app.core.model.slice
 
 internal object StepQuizCodeBlanksResolver {
-    private const val ONBOARDING_STEP_ID = 47329L
-
     private const val MINIMUM_POSSIBLE_INDEX_FOR_ELIF_AND_ELSE_STATEMENTS = 2
 
-    fun isOnboardingAvailable(step: Step): Boolean =
-        step.id == ONBOARDING_STEP_ID
-
     fun isVariableSuggestionsAvailable(step: Step): Boolean =
         step.block.options.codeBlanksVariables?.isNotEmpty() == true
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
index 2bf809df5..23f426c7c 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
@@ -15,10 +15,21 @@ sealed interface StepQuizCodeBlanksViewState {
         internal val onboardingState: OnboardingState = OnboardingState.Unavailable
     ) : StepQuizCodeBlanksViewState {
         val isActionButtonsHidden: Boolean
-            get() = onboardingState != OnboardingState.Unavailable
+            get() = when (onboardingState) {
+                is OnboardingState.PrintSuggestionAndCallToAction -> true
+                OnboardingState.HighlightDeleteButton,
+                OnboardingState.HighlightSpaceButton,
+                OnboardingState.Unavailable -> false
+            }
+
+        val isDeleteButtonHighlightEffectActive: Boolean
+            get() = isDeleteButtonEnabled && onboardingState == OnboardingState.HighlightDeleteButton
+
+        val isSpaceButtonHighlightEffectActive: Boolean
+            get() = !isSpaceButtonHidden && onboardingState == OnboardingState.HighlightSpaceButton
 
         val isSuggestionsHighlightEffectActive: Boolean
-            get() = onboardingState == OnboardingState.HighlightSuggestions
+            get() = onboardingState == OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
     }
 
     sealed interface CodeBlockItem {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/presentation/StepQuizChildFeatureReducerStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/presentation/StepQuizChildFeatureReducerStub.kt
index 5582cfbce..f33924868 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/presentation/StepQuizChildFeatureReducerStub.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/presentation/StepQuizChildFeatureReducerStub.kt
@@ -5,10 +5,11 @@ import org.hyperskill.app.step_quiz.presentation.StepQuizChildFeatureReducer
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer
 import org.hyperskill.app.step_quiz_toolbar.presentation.StepQuizToolbarReducer
+import org.hyperskill.step_quiz_code_blanks.presentation.stub
 
 internal fun StepQuizChildFeatureReducer.Companion.stub(stepRoute: StepRoute) =
     StepQuizChildFeatureReducer(
         stepQuizHintsReducer = StepQuizHintsReducer(stepRoute),
         stepQuizToolbarReducer = StepQuizToolbarReducer(stepRoute),
-        stepQuizCodeBlanksReducer = StepQuizCodeBlanksReducer(stepRoute)
+        stepQuizCodeBlanksReducer = StepQuizCodeBlanksReducer.stub(stepRoute)
     )
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt
index 9c864bf95..a9acc7b22 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -12,7 +11,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR
 import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState
 
 class StepQuizCodeBlanksReducerCodeBlockChildClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `CodeBlockChildClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt
index 84deb40e1..5e57731ff 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -12,7 +11,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR
 import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState
 
 class StepQuizCodeBlanksReducerCodeBlockClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `CodeBlockClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt
index 9e16f08b9..e2b2572d2 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
@@ -11,7 +10,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 
 class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `DecreaseIndentLevelButtonClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt
index d8492a778..bd2ef9dde 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -12,7 +11,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 
 class StepQuizCodeBlanksReducerDeleteButtonClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `DeleteButtonClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt
index b25d8cd19..d14100288 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -12,7 +11,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 
 class StepQuizCodeBlanksReducerEnterButtonClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `EnterButtonClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
index ef8ad0c50..52f60c050 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
@@ -5,7 +5,6 @@ import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 import org.hyperskill.app.step.domain.model.Block
 import org.hyperskill.app.step.domain.model.Step
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature
@@ -13,7 +12,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR
 import org.hyperskill.step.domain.model.stub
 
 class StepQuizCodeBlanksReducerInitializeTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `Initialize should return Content state with active Blank and Print and Variable and If suggestions`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
index e6f4e5daf..ccc3b5011 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
@@ -4,7 +4,6 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 import org.hyperskill.app.step.domain.model.Step
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
@@ -14,7 +13,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR
 import org.hyperskill.step.domain.model.stub
 
 class StepQuizCodeBlanksReducerOnboardingTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `Onboarding should be unavailable`() {
@@ -29,15 +28,41 @@ class StepQuizCodeBlanksReducerOnboardingTest {
     }
 
     @Test
-    fun `Onboarding should be available`() {
+    fun `Onboarding should be available for print suggestion`() {
+        setOf(47329L, 50968L).forEach { stepId ->
+            val initialState = StepQuizCodeBlanksFeature.State.Idle
+            val (state, _) = reducer.reduce(
+                initialState,
+                StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = stepId))
+            )
+
+            assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
+            assertTrue(state.onboardingState is OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions)
+        }
+    }
+
+    @Test
+    fun `Onboarding should be available for delete button`() {
+        val initialState = StepQuizCodeBlanksFeature.State.Idle
+        val (state, _) = reducer.reduce(
+            initialState,
+            StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 50969L))
+        )
+
+        assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
+        assertTrue(state.onboardingState is OnboardingState.HighlightDeleteButton)
+    }
+
+    @Test
+    fun `Onboarding should be available for space button`() {
         val initialState = StepQuizCodeBlanksFeature.State.Idle
         val (state, _) = reducer.reduce(
             initialState,
-            StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 47329))
+            StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 50970L))
         )
 
         assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
-        assertTrue(state.onboardingState is OnboardingState.HighlightSuggestions)
+        assertTrue(state.onboardingState is OnboardingState.HighlightSpaceButton)
     }
 
     @Test
@@ -55,13 +80,13 @@ class StepQuizCodeBlanksReducerOnboardingTest {
                     )
                 )
             ),
-            onboardingState = OnboardingState.HighlightSuggestions
+            onboardingState = OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
         )
 
         val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion)
         val (state, _) = reducer.reduce(initialState, message)
 
         assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
-        assertEquals(OnboardingState.HighlightCallToActionButton, state.onboardingState)
+        assertEquals(OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton, state.onboardingState)
     }
 }
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
index 3c8166d55..232d64527 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
@@ -5,7 +5,6 @@ import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 import org.hyperskill.app.step.domain.model.Block
 import org.hyperskill.app.step.domain.model.Step
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -15,7 +14,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR
 import org.hyperskill.step.domain.model.stub
 
 class StepQuizCodeBlanksReducerSpaceButtonClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `SpaceButtonClicked should not update state if state is not Content`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerStub.kt
new file mode 100644
index 000000000..154d83955
--- /dev/null
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerStub.kt
@@ -0,0 +1,10 @@
+package org.hyperskill.step_quiz_code_blanks.presentation
+
+import org.hyperskill.app.step.domain.model.StepRoute
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksOnboardingReducer
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
+
+internal fun StepQuizCodeBlanksReducer.Companion.stub(
+    stepRoute: StepRoute = StepRoute.Learn.Step(1, null)
+): StepQuizCodeBlanksReducer =
+    StepQuizCodeBlanksReducer(stepRoute, StepQuizCodeBlanksOnboardingReducer())
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
index 5e3970aff..a71716ffd 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
@@ -3,7 +3,6 @@ package org.hyperskill.step_quiz_code_blanks.presentation
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
-import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
@@ -12,7 +11,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 
 class StepQuizCodeBlanksReducerSuggestionClickedTest {
-    private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null))
+    private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
     fun `SuggestionClicked should not update state if no active code block`() {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
index ac692810f..2a9f541c8 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
@@ -8,32 +8,87 @@ import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksVie
 
 class StepQuizCodeBlanksViewStateTest {
     @Test
-    fun `isActionButtonsHidden should be true when onboarding is available`() {
-        val viewState = stubContentViewState(onboardingState = OnboardingState.HighlightSuggestions)
-        assertTrue(viewState.isActionButtonsHidden)
+    fun `isActionButtonsHidden should be true when onboarding PrintSuggestionAndCallToAction`() {
+        listOf(
+            OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions,
+            OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton
+        ).forEach { onboardingState ->
+            val viewState = stubContentViewState(onboardingState = onboardingState)
+            assertTrue(viewState.isActionButtonsHidden)
+        }
     }
 
     @Test
-    fun `isActionButtonsHidden should be false when onboarding is unavailable`() {
-        val viewState = stubContentViewState(onboardingState = OnboardingState.Unavailable)
-        assertFalse(viewState.isActionButtonsHidden)
+    fun `isActionButtonsHidden should be false when onboarding is not PrintSuggestionAndCallToAction`() {
+        listOf(
+            OnboardingState.HighlightDeleteButton,
+            OnboardingState.HighlightSpaceButton,
+            OnboardingState.Unavailable
+        ).forEach { onboardingState ->
+            val viewState = stubContentViewState(onboardingState = onboardingState)
+            assertFalse(viewState.isActionButtonsHidden)
+        }
+    }
+
+    /* ktlint-disable */
+    @Test
+    fun `isDeleteButtonHighlightEffectActive should be true when onboardingState is HighlightDeleteButton and isDeleteButtonEnabled`() {
+        val viewState = stubContentViewState(
+            isDeleteButtonEnabled = true,
+            onboardingState = OnboardingState.HighlightDeleteButton
+        )
+        assertTrue(viewState.isDeleteButtonHighlightEffectActive)
+    }
+
+    /* ktlint-disable */
+    @Test
+    fun `isDeleteButtonHighlightEffectActive should be false when onboardingState is HighlightDeleteButton and isDeleteButtonEnabled is false`() {
+        val viewState = stubContentViewState(
+            isDeleteButtonEnabled = false,
+            onboardingState = OnboardingState.HighlightDeleteButton
+        )
+        assertFalse(viewState.isDeleteButtonHighlightEffectActive)
+    }
+
+    /* ktlint-disable */
+    @Test
+    fun `isSpaceButtonHighlightEffectActive should be true when onboardingState is HighlightSpaceButton and isSpaceButtonHidden is false`() {
+        val viewState = stubContentViewState(
+            onboardingState = OnboardingState.HighlightSpaceButton
+        )
+        assertTrue(viewState.isSpaceButtonHighlightEffectActive)
+    }
+
+    /* ktlint-disable */
+    @Test
+    fun `isSpaceButtonHighlightEffectActive should be false when onboardingState is HighlightSpaceButton and isSpaceButtonHidden is true`() {
+        val viewState = stubContentViewState(
+            isSpaceButtonHidden = true,
+            onboardingState = OnboardingState.HighlightSpaceButton
+        )
+        assertFalse(viewState.isSpaceButtonHighlightEffectActive)
     }
 
     @Test
     fun `isSuggestionsHighlightEffectActive should be true when onboardingState is HighlightSuggestions`() {
-        val viewState = stubContentViewState(onboardingState = OnboardingState.HighlightSuggestions)
+        val viewState = stubContentViewState(
+            onboardingState = OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+        )
         assertTrue(viewState.isSuggestionsHighlightEffectActive)
     }
 
     private fun stubContentViewState(
+        isDeleteButtonEnabled: Boolean = false,
+        isSpaceButtonHidden: Boolean = false,
+        isDecreaseIndentLevelButtonHidden: Boolean = false,
         onboardingState: OnboardingState
     ): StepQuizCodeBlanksViewState.Content =
         StepQuizCodeBlanksViewState.Content(
             codeBlocks = emptyList(),
             suggestions = emptyList(),
-            isDeleteButtonEnabled = false,
-            isSpaceButtonHidden = false,
-            isDecreaseIndentLevelButtonHidden = false,
+            isDeleteButtonEnabled = isDeleteButtonEnabled,
+            isSpaceButtonHidden = isSpaceButtonHidden,
+            isDecreaseIndentLevelButtonHidden = isDecreaseIndentLevelButtonHidden,
             onboardingState = onboardingState
         )
 }
\ No newline at end of file

From 11c253d26d72b1a0ae57453d440b6da8d59d7167 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Wed, 18 Sep 2024 06:15:39 +0000
Subject: [PATCH 08/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 1e0922514..0eb08e168 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '550'
\ No newline at end of file
+versionCode = '551'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index d21b1b795..ad14f82f8 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>579</string>
+	<string>580</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index f2ae61048..4d133282c 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 579;
+				CURRENT_PROJECT_VERSION = 580;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 39255604d..2bdea9a12 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>579</string>
+	<string>580</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 518d4f6da..43d63ab6d 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>579</string>
+	<string>580</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 7d4e1781f..42d11e9da 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>579</string>
+	<string>580</string>
 </dict>
 </plist>

From e63831ddbee3704fe27bff8a7ea7857e2852291a Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Wed, 18 Sep 2024 18:45:55 +0900
Subject: [PATCH 09/27] ALTAPPS-1353: Make code_blanks_template field String

---
 .../Shared/Model/BlockOptionsExtensions.swift |  4 +--
 .../hyperskill/app/step/domain/model/Block.kt | 19 ++++++++++--
 .../template/CodeBlanksTemplateMapper.kt      | 30 +++++++++++--------
 .../template/CodeBlanksTemplateMapperTest.kt  | 12 +++++---
 4 files changed, 44 insertions(+), 21 deletions(-)

diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
index 68abcdd22..8b4aaa2d5 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
@@ -15,7 +15,7 @@ extension Block.Options {
         codeBlanksVariables: [String]? = nil,
         codeBlanksOperations: [String]? = nil,
         codeBlanksEnabled: Bool? = nil,
-        codeBlanksTemplate: [CodeBlockTemplateEntry]? = nil
+        codeBlanksTemplateString: String? = nil
     ) {
         self.init(
             isMultipleChoice: isMultipleChoice.flatMap(KotlinBoolean.init(value:)),
@@ -29,7 +29,7 @@ extension Block.Options {
             codeBlanksVariables: codeBlanksVariables,
             codeBlanksOperations: codeBlanksOperations,
             codeBlanksEnabled: codeBlanksEnabled.flatMap(KotlinBoolean.init(value:)),
-            codeBlanksTemplate: codeBlanksTemplate
+            codeBlanksTemplateString: codeBlanksTemplateString
         )
     }
     // swiftlint:enable discouraged_optional_boolean
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
index 9d0b5a8c8..57d2b5fc0 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
@@ -2,7 +2,9 @@ package org.hyperskill.app.step.domain.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
 import org.hyperskill.app.code.domain.model.ProgrammingLanguage
+import org.hyperskill.app.network.injection.NetworkModule
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.template.CodeBlockTemplateEntry
 
 @Serializable
@@ -39,7 +41,7 @@ data class Block(
         @SerialName("code_blanks_enabled")
         val codeBlanksEnabled: Boolean? = null,
         @SerialName("code_blanks_template")
-        val codeBlanksTemplate: List<CodeBlockTemplateEntry>? = null
+        val codeBlanksTemplateString: String? = null
     ) {
         val samples: List<Sample>?
             get() = internalSamples?.mapNotNull {
@@ -67,4 +69,17 @@ data class Block(
 }
 
 val Block.Options.programmingLanguage: ProgrammingLanguage?
-    get() = language?.let(ProgrammingLanguage::of)
\ No newline at end of file
+    get() = language?.let(ProgrammingLanguage::of)
+
+internal fun Block.Options.decodeCodeBlanksTemplateString(
+    json: Json = NetworkModule.provideJson()
+): List<CodeBlockTemplateEntry> {
+    if (codeBlanksTemplateString.isNullOrBlank()) {
+        return emptyList()
+    }
+    return try {
+        json.decodeFromString<List<CodeBlockTemplateEntry>>(codeBlanksTemplateString)
+    } catch (e: Exception) {
+        emptyList()
+    }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
index a9ad51611..eef0e448c 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
@@ -2,6 +2,7 @@ package org.hyperskill.app.step_quiz_code_blanks.domain.model.template
 
 import kotlin.math.max
 import org.hyperskill.app.step.domain.model.Step
+import org.hyperskill.app.step.domain.model.decodeCodeBlanksTemplateString
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
@@ -14,24 +15,27 @@ internal object CodeBlanksTemplateMapper {
     private const val MATH_EXPRESSIONS_TEMPLATE_STEP_ID = 47580L // ALTAPPS-1324
 
     fun map(step: Step): List<CodeBlock> =
-        when {
-            step.id == MATH_EXPRESSIONS_TEMPLATE_STEP_ID -> createMathExpressionsCodeBlocks(step)
-            isCodeBlanksTemplateAvailable(step) -> parseCodeBlanksTemplate(step)
-            else -> emptyList()
+        if (step.id == MATH_EXPRESSIONS_TEMPLATE_STEP_ID) {
+            createMathExpressionsCodeBlocks(step)
+        } else {
+            step.block.options.decodeCodeBlanksTemplateString()
+                .takeIf(::isCodeBlanksTemplateAvailable)
+                ?.let { mapCodeBlanksTemplate(it, step) }
+                ?: emptyList()
         }
 
-    private fun isCodeBlanksTemplateAvailable(step: Step): Boolean {
-        val codeBlockTemplateEntries = step.block.options.codeBlanksTemplate ?: return false
-        return codeBlockTemplateEntries.none { it.type == CodeBlockTemplateEntryType.UNKNOWN }
-    }
+    private fun isCodeBlanksTemplateAvailable(codeBlanksTemplate: List<CodeBlockTemplateEntry>): Boolean =
+        codeBlanksTemplate.none { it.type == CodeBlockTemplateEntryType.UNKNOWN }
 
-    private fun parseCodeBlanksTemplate(step: Step): List<CodeBlock> {
-        val codeBlockTemplateEntries = step.block.options.codeBlanksTemplate
-            ?.filter { it.type != CodeBlockTemplateEntryType.UNKNOWN }
-        return if (codeBlockTemplateEntries.isNullOrEmpty()) {
+    private fun mapCodeBlanksTemplate(
+        codeBlanksTemplate: List<CodeBlockTemplateEntry>,
+        step: Step
+    ): List<CodeBlock> {
+        val supportedEntries = codeBlanksTemplate.filter { it.type != CodeBlockTemplateEntryType.UNKNOWN }
+        return if (supportedEntries.isEmpty()) {
             emptyList()
         } else {
-            codeBlockTemplateEntries.map { mapCodeBlockTemplateEntry(entry = it, step = step) }
+            supportedEntries.map { mapCodeBlockTemplateEntry(entry = it, step = step) }
         }
     }
 
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/template/CodeBlanksTemplateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/template/CodeBlanksTemplateMapperTest.kt
index aede0c7a7..7420ac30b 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/template/CodeBlanksTemplateMapperTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/template/CodeBlanksTemplateMapperTest.kt
@@ -3,6 +3,8 @@ package org.hyperskill.step_quiz_code_blanks.template
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
+import kotlinx.serialization.encodeToString
+import org.hyperskill.app.network.injection.NetworkModule
 import org.hyperskill.app.step.domain.model.Block
 import org.hyperskill.app.step.domain.model.Step
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
@@ -33,9 +35,7 @@ class CodeBlanksTemplateMapperTest {
             id = 1,
             block = Block.stub(
                 options = Block.Options(
-                    codeBlanksTemplate = listOf(
-                        CodeBlockTemplateEntry(type = CodeBlockTemplateEntryType.UNKNOWN)
-                    )
+                    codeBlanksTemplateString = "[{\"type\":\"test\"}]"
                 )
             )
         )
@@ -63,9 +63,13 @@ class CodeBlanksTemplateMapperTest {
                 children = emptyList()
             )
         )
+
+        val json = NetworkModule.provideJson()
+        val codeBlanksTemplateString = json.encodeToString(codeBlanksTemplate)
+
         val step = Step.stub(
             id = 1,
-            block = Block.stub(options = Block.Options(codeBlanksTemplate = codeBlanksTemplate))
+            block = Block.stub(options = Block.Options(codeBlanksTemplateString = codeBlanksTemplateString))
         )
 
         val expectedCodeBlocks = listOf(

From f6abb66a571c65f83fa0eb7bc882c2d037131a74 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Wed, 18 Sep 2024 09:46:40 +0000
Subject: [PATCH 10/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 0eb08e168..a934e12bf 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '551'
\ No newline at end of file
+versionCode = '552'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index ad14f82f8..f31abd779 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>580</string>
+	<string>581</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 4d133282c..b571d08b8 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 580;
+				CURRENT_PROJECT_VERSION = 581;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 2bdea9a12..16139c0a7 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>580</string>
+	<string>581</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 43d63ab6d..f2222fef9 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>580</string>
+	<string>581</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 42d11e9da..9a1fc1a23 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>580</string>
+	<string>581</string>
 </dict>
 </plist>

From a72c824183ebffdc1e3fbb6764fb87b455c97fbc Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Wed, 18 Sep 2024 20:21:11 +0900
Subject: [PATCH 11/27] ALTAPPS-1357: Fix
 StepQuizCodeBlanksReducer.handleSpaceButtonClicked newChildSuggestions

---
 config/detekt/baseline.xml                                     | 2 ++
 .../presentation/StepQuizCodeBlanksReducer.kt                  | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index b3066984b..bb7d6eeb2 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -259,12 +259,14 @@
     <ID>ReturnCount:TopicsRepetitionsActionDispatcher.kt$TopicsRepetitionsActionDispatcher$override suspend fun doSuspendableAction(action: Action)</ID>
     <ID>SpreadOperator:StepQuizViewStateDelegateFactory.kt$StepQuizViewStateDelegateFactory$( *listOfNotNull( fragmentStepQuizBinding.stepQuizFeedbackBlocks.root, descriptionBinding?.stepQuizDescription, fragmentStepQuizBinding.stepQuizButtons.stepQuizSubmitButton, *quizViews ).toTypedArray() )</ID>
     <ID>SpreadOperator:StepQuizViewStateDelegateFactory.kt$StepQuizViewStateDelegateFactory$( *listOfNotNull( skeletonView, descriptionBinding?.stepQuizDescriptionSkeleton ).toTypedArray() )</ID>
+    <ID>SwallowedException:Block.kt$e: Exception</ID>
     <ID>SwallowedException:NotificationExtensions.kt$e: ActivityNotFoundException</ID>
     <ID>SwallowedException:ProfileSettingsDialogFragment.kt$ProfileSettingsDialogFragment$e: ActivityNotFoundException</ID>
     <ID>SwallowedException:PushNotificationData.kt$PushNotificationData$e: Exception</ID>
     <ID>SwallowedException:TimeIntervalPickerDialogFragment.kt$TimeIntervalPickerDialogFragment$exception: Exception</ID>
     <ID>ThrowsCount:FillBlanksResolver.kt$FillBlanksResolver$@Throws(InvalidFillBlanksConfigException::class) fun resolve(dataset: Dataset): FillBlanksMode</ID>
     <ID>TooGenericExceptionCaught:BaseStateRepository.kt$BaseStateRepository$e: Exception</ID>
+    <ID>TooGenericExceptionCaught:Block.kt$e: Exception</ID>
     <ID>TooGenericExceptionCaught:LatexWebView.kt$LatexWebView$e: Exception</ID>
     <ID>TooGenericExceptionCaught:PushNotificationData.kt$PushNotificationData$e: Exception</ID>
     <ID>TooGenericExceptionCaught:PushNotificationsInteractor.kt$PushNotificationsInteractor$e: Exception</ID>
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
index 90eb004db..455559ee4 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
@@ -615,7 +615,8 @@ class StepQuizCodeBlanksReducer(
                         activeChild.selectedSuggestion in state.codeBlanksOperationsSuggestions ->
                             state.codeBlanksVariablesSuggestions + state.codeBlanksStringsSuggestions
 
-                        else -> emptyList()
+                        else ->
+                            state.codeBlanksOperationsSuggestions + state.codeBlanksVariablesAndStringsSuggestions
                     }
 
                     val newChild = CodeBlockChild.SelectSuggestion(

From f44e719001686a726f996fb2e78e9d1da5b922ce Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Wed, 18 Sep 2024 11:22:03 +0000
Subject: [PATCH 12/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index a934e12bf..2a567f6ff 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '552'
\ No newline at end of file
+versionCode = '553'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index f31abd779..d506533a4 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>581</string>
+	<string>582</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index b571d08b8..89d181b46 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 581;
+				CURRENT_PROJECT_VERSION = 582;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 16139c0a7..88a84db3f 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>581</string>
+	<string>582</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index f2222fef9..eb2e760b6 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>581</string>
+	<string>582</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 9a1fc1a23..129c4dbe9 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>581</string>
+	<string>582</string>
 </dict>
 </plist>

From c6af5a65f8995be85e0f97a33e7520efa3326e6c Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Thu, 19 Sep 2024 12:12:07 +0900
Subject: [PATCH 13/27] Shared, iOS: Code blanks mechanics onboarding final
 (#1185)

^ALTAPPS-1357
---
 .../StepQuizCodeBlanksActionButton.swift      | 15 ++++--
 .../StepQuizCodeBlanksActionButtonsView.swift |  8 ++-
 .../StepQuizCodeBlanksCodeBlocksView.swift    |  1 +
 .../presentation/StepQuizCodeBlanksFeature.kt | 17 ++++---
 .../StepQuizCodeBlanksOnboardingReducer.kt    | 50 ++++++++++++++----
 .../presentation/StepQuizCodeBlanksReducer.kt | 51 ++++++++++++-------
 .../StepQuizCodeBlanksViewStateMapper.kt      |  2 +-
 .../view/model/StepQuizCodeBlanksViewState.kt |  8 ++-
 ...StepQuizCodeBlanksReducerOnboardingTest.kt | 33 +++++++++---
 ...CodeBlanksReducerSpaceButtonClickedTest.kt |  2 +-
 ...zCodeBlanksReducerSuggestionClickedTest.kt | 10 ++--
 .../view/StepQuizCodeBlanksViewStateTest.kt   | 19 +++++--
 12 files changed, 155 insertions(+), 61 deletions(-)

diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
index 54ae95110..97288d952 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift
@@ -58,8 +58,15 @@ extension StepQuizCodeBlanksActionButton {
         )
     }
 
-    static func enter(action: @escaping () -> Void) -> StepQuizCodeBlanksActionButton {
-        StepQuizCodeBlanksActionButton(imageSystemName: "return", action: action)
+    static func enter(
+        isAnimationEffectActive: Bool,
+        action: @escaping () -> Void
+    ) -> StepQuizCodeBlanksActionButton {
+        StepQuizCodeBlanksActionButton(
+            imageSystemName: "return",
+            isAnimationEffectActive: isAnimationEffectActive,
+            action: action
+        )
     }
 
     static func space(
@@ -98,14 +105,14 @@ extension StepQuizCodeBlanksActionButton {
     VStack {
         HStack {
             StepQuizCodeBlanksActionButton.delete(isAnimationEffectActive: false, action: {})
-            StepQuizCodeBlanksActionButton.enter(action: {})
+            StepQuizCodeBlanksActionButton.enter(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.space(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {})
         }
 
         HStack {
             StepQuizCodeBlanksActionButton.delete(isAnimationEffectActive: false, action: {})
-            StepQuizCodeBlanksActionButton.enter(action: {})
+            StepQuizCodeBlanksActionButton.enter(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.space(isAnimationEffectActive: false, action: {})
             StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {})
         }
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
index f06b0d607..a03630e7c 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift
@@ -6,6 +6,7 @@ struct StepQuizCodeBlanksActionButtonsView: View {
     let isDecreaseIndentLevelButtonHidden: Bool
 
     let isDeleteButtonHighlightEffectActive: Bool
+    let isEnterButtonHighlightEffectActive: Bool
     let isSpaceButtonHighlightEffectActive: Bool
 
     let onSpaceTap: () -> Void
@@ -38,7 +39,10 @@ struct StepQuizCodeBlanksActionButtonsView: View {
                 .disabled(!isDeleteButtonEnabled)
 
             StepQuizCodeBlanksActionButton
-                .enter(action: onEnterTap)
+                .enter(
+                    isAnimationEffectActive: isEnterButtonHighlightEffectActive,
+                    action: onEnterTap
+                )
         }
         .padding(.horizontal)
     }
@@ -52,6 +56,7 @@ struct StepQuizCodeBlanksActionButtonsView: View {
             isSpaceButtonHidden: false,
             isDecreaseIndentLevelButtonHidden: false,
             isDeleteButtonHighlightEffectActive: false,
+            isEnterButtonHighlightEffectActive: false,
             isSpaceButtonHighlightEffectActive: true,
             onSpaceTap: {},
             onDeleteTap: {},
@@ -64,6 +69,7 @@ struct StepQuizCodeBlanksActionButtonsView: View {
             isSpaceButtonHidden: true,
             isDecreaseIndentLevelButtonHidden: true,
             isDeleteButtonHighlightEffectActive: true,
+            isEnterButtonHighlightEffectActive: true,
             isSpaceButtonHighlightEffectActive: false,
             onSpaceTap: {},
             onDeleteTap: {},
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
index acb66cf80..27be02c49 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift
@@ -40,6 +40,7 @@ struct StepQuizCodeBlanksCodeBlocksView: View {
                     isSpaceButtonHidden: state.isSpaceButtonHidden,
                     isDecreaseIndentLevelButtonHidden: state.isDecreaseIndentLevelButtonHidden,
                     isDeleteButtonHighlightEffectActive: state.isDeleteButtonHighlightEffectActive,
+                    isEnterButtonHighlightEffectActive: state.isEnterButtonHighlightEffectActive,
                     isSpaceButtonHighlightEffectActive: state.isSpaceButtonHighlightEffectActive,
                     onSpaceTap: onSpaceTap,
                     onDeleteTap: onDeleteTap,
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
index 667193697..421cdaa10 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt
@@ -23,16 +23,16 @@ object StepQuizCodeBlanksFeature {
         ) : State {
             companion object;
 
-            internal val codeBlanksStringsSuggestions: List<Suggestion.ConstantString> =
+            internal val stringsSuggestions: List<Suggestion.ConstantString> =
                 step.codeBlanksStringsSuggestions()
 
-            internal val codeBlanksVariablesSuggestions: List<Suggestion.ConstantString> =
+            internal val variablesSuggestions: List<Suggestion.ConstantString> =
                 step.codeBlanksVariablesSuggestions()
 
-            internal val codeBlanksVariablesAndStringsSuggestions: List<Suggestion.ConstantString> =
-                codeBlanksVariablesSuggestions + codeBlanksStringsSuggestions
+            internal val variablesAndStringsSuggestions: List<Suggestion.ConstantString> =
+                variablesSuggestions + stringsSuggestions
 
-            internal val codeBlanksOperationsSuggestions: List<Suggestion.ConstantString> =
+            internal val operationsSuggestions: List<Suggestion.ConstantString> =
                 step.codeBlanksOperationsSuggestions()
         }
     }
@@ -41,11 +41,12 @@ object StepQuizCodeBlanksFeature {
         data object Unavailable : OnboardingState
 
         data object HighlightDeleteButton : OnboardingState
+        data object HighlightEnterButton : OnboardingState
         data object HighlightSpaceButton : OnboardingState
 
-        sealed interface PrintSuggestionAndCallToAction : OnboardingState {
-            data object HighlightSuggestions : PrintSuggestionAndCallToAction
-            data object HighlightCallToActionButton : PrintSuggestionAndCallToAction
+        sealed interface FirstProgram : OnboardingState {
+            data object HighlightSuggestions : FirstProgram
+            data object HighlightCallToActionButton : FirstProgram
         }
     }
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
index 46e84e782..4789e79e8 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksOnboardingReducer.kt
@@ -7,20 +7,23 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.State
 
+private typealias StepQuizCodeBlanksOnboardingReducerResult = Pair<OnboardingState, Set<Action>>
+
 class StepQuizCodeBlanksOnboardingReducer {
     companion object {
-        private const val DELETE_BUTTON_STEP_ID = 50969L
-        private const val SPACE_BUTTON_STEP_ID = 50970L
-        private val PRINT_SUGGESTION_AND_CALL_TO_ACTION_STEP_IDS = setOf(47329L, 50968L)
+        internal val FIRST_PROGRAM_STEP_IDS = setOf(47329L, 50984L)
+        internal const val DELETE_BUTTON_STEP_ID = 50986L
+        internal const val ENTER_BUTTON_STEP_ID = 50985L
+        internal const val SPACE_BUTTON_STEP_ID = 50997L
     }
 
     internal fun reduceInitializeMessage(
         message: InternalMessage.Initialize
     ): OnboardingState =
         when (message.step.id) {
-            in PRINT_SUGGESTION_AND_CALL_TO_ACTION_STEP_IDS ->
-                OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+            in FIRST_PROGRAM_STEP_IDS -> OnboardingState.FirstProgram.HighlightSuggestions
             DELETE_BUTTON_STEP_ID -> OnboardingState.HighlightDeleteButton
+            ENTER_BUTTON_STEP_ID -> OnboardingState.HighlightEnterButton
             SPACE_BUTTON_STEP_ID -> OnboardingState.HighlightSpaceButton
             else -> OnboardingState.Unavailable
         }
@@ -29,13 +32,13 @@ class StepQuizCodeBlanksOnboardingReducer {
         state: State.Content,
         activeCodeBlock: CodeBlock?,
         newCodeBlock: CodeBlock
-    ): Pair<OnboardingState, Set<Action>> {
-        val isFulfilledOnboardingPrintCodeBlock =
-            state.onboardingState is OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions &&
+    ): StepQuizCodeBlanksOnboardingReducerResult {
+        val isFulfilledFirstProgramOnboarding =
+            state.onboardingState is OnboardingState.FirstProgram.HighlightSuggestions &&
                 activeCodeBlock is CodeBlock.Print && activeCodeBlock.hasAnyUnselectedChild() &&
                 newCodeBlock is CodeBlock.Print && newCodeBlock.areAllChildrenSelected()
-        return if (isFulfilledOnboardingPrintCodeBlock) {
-            OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton to
+        return if (isFulfilledFirstProgramOnboarding) {
+            OnboardingState.FirstProgram.HighlightCallToActionButton to
                 setOf(
                     InternalAction.ParentFeatureActionRequested(
                         StepQuizCodeBlanksFeature.ParentFeatureAction.HighlightCallToActionButton
@@ -45,4 +48,31 @@ class StepQuizCodeBlanksOnboardingReducer {
             state.onboardingState to emptySet()
         }
     }
+
+    internal fun reduceDeleteButtonClickedMessage(
+        state: State.Content
+    ): StepQuizCodeBlanksOnboardingReducerResult =
+        if (state.onboardingState is OnboardingState.HighlightDeleteButton) {
+            OnboardingState.Unavailable to emptySet()
+        } else {
+            state.onboardingState to emptySet()
+        }
+
+    internal fun reduceEnterButtonClickedMessage(
+        state: State.Content
+    ): StepQuizCodeBlanksOnboardingReducerResult =
+        if (state.onboardingState is OnboardingState.HighlightEnterButton) {
+            OnboardingState.Unavailable to emptySet()
+        } else {
+            state.onboardingState to emptySet()
+        }
+
+    internal fun reduceSpaceButtonClickedMessage(
+        state: State.Content
+    ): StepQuizCodeBlanksOnboardingReducerResult =
+        if (state.onboardingState is OnboardingState.HighlightSpaceButton) {
+            OnboardingState.Unavailable to emptySet()
+        } else {
+            state.onboardingState to emptySet()
+        }
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
index 455559ee4..ce3e9db99 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
@@ -87,7 +87,7 @@ class StepQuizCodeBlanksReducer(
                             children = listOf(
                                 CodeBlockChild.SelectSuggestion(
                                     isActive = true,
-                                    suggestions = state.codeBlanksVariablesAndStringsSuggestions,
+                                    suggestions = state.variablesAndStringsSuggestions,
                                     selectedSuggestion = null
                                 )
                             )
@@ -98,12 +98,12 @@ class StepQuizCodeBlanksReducer(
                             children = listOf(
                                 CodeBlockChild.SelectSuggestion(
                                     isActive = true,
-                                    suggestions = state.codeBlanksVariablesSuggestions,
+                                    suggestions = state.variablesSuggestions,
                                     selectedSuggestion = null
                                 ),
                                 CodeBlockChild.SelectSuggestion(
                                     isActive = false,
-                                    suggestions = state.codeBlanksStringsSuggestions,
+                                    suggestions = state.stringsSuggestions,
                                     selectedSuggestion = null
                                 )
                             )
@@ -114,7 +114,7 @@ class StepQuizCodeBlanksReducer(
                             children = listOf(
                                 CodeBlockChild.SelectSuggestion(
                                     isActive = true,
-                                    suggestions = state.codeBlanksVariablesAndStringsSuggestions,
+                                    suggestions = state.variablesAndStringsSuggestions,
                                     selectedSuggestion = null
                                 )
                             )
@@ -125,7 +125,7 @@ class StepQuizCodeBlanksReducer(
                             children = listOf(
                                 CodeBlockChild.SelectSuggestion(
                                     isActive = true,
-                                    suggestions = state.codeBlanksVariablesAndStringsSuggestions,
+                                    suggestions = state.variablesAndStringsSuggestions,
                                     selectedSuggestion = null
                                 )
                             )
@@ -511,7 +511,13 @@ class StepQuizCodeBlanksReducer(
             }
         }
 
-        return state.copy(codeBlocks = newCodeBlocks) to actions
+        val (onboardingState, onboardingActions) =
+            stepQuizCodeBlanksOnboardingReducer.reduceDeleteButtonClickedMessage(state)
+
+        return state.copy(
+            codeBlocks = newCodeBlocks,
+            onboardingState = onboardingState
+        ) to actions + onboardingActions
     }
 
     private fun handleEnterButtonClicked(
@@ -564,7 +570,13 @@ class StepQuizCodeBlanksReducer(
                 )
             }
 
-            state.copy(codeBlocks = newCodeBlocks) to actions
+            val (onboardingState, onboardingActions) =
+                stepQuizCodeBlanksOnboardingReducer.reduceEnterButtonClickedMessage(state)
+
+            state.copy(
+                codeBlocks = newCodeBlocks,
+                onboardingState = onboardingState
+            ) to actions + onboardingActions
         } else {
             state to actions
         }
@@ -603,20 +615,19 @@ class StepQuizCodeBlanksReducer(
 
                     val newChildSuggestions = when {
                         activeChild.selectedSuggestion?.isOpeningParentheses == true ->
-                            state.codeBlanksVariablesSuggestions + state.codeBlanksStringsSuggestions
+                            state.variablesSuggestions + state.stringsSuggestions
 
                         activeChild.selectedSuggestion?.isClosingParentheses == true ->
-                            state.codeBlanksOperationsSuggestions
+                            state.operationsSuggestions
 
-                        activeChild.selectedSuggestion in state.codeBlanksStringsSuggestions ||
-                            activeChild.selectedSuggestion in state.codeBlanksVariablesSuggestions ->
-                            state.codeBlanksOperationsSuggestions
+                        activeChild.selectedSuggestion in state.stringsSuggestions ||
+                            activeChild.selectedSuggestion in state.variablesSuggestions ->
+                            state.operationsSuggestions
 
-                        activeChild.selectedSuggestion in state.codeBlanksOperationsSuggestions ->
-                            state.codeBlanksVariablesSuggestions + state.codeBlanksStringsSuggestions
+                        activeChild.selectedSuggestion in state.operationsSuggestions ->
+                            state.variablesSuggestions + state.stringsSuggestions
 
-                        else ->
-                            state.codeBlanksOperationsSuggestions + state.codeBlanksVariablesAndStringsSuggestions
+                        else -> state.operationsSuggestions + state.variablesAndStringsSuggestions
                     }
 
                     val newChild = CodeBlockChild.SelectSuggestion(
@@ -644,7 +655,13 @@ class StepQuizCodeBlanksReducer(
             }
         }
 
-        return state.copy(codeBlocks = newCodeBlocks) to actions
+        val (onboardingState, onboardingActions) =
+            stepQuizCodeBlanksOnboardingReducer.reduceSpaceButtonClickedMessage(state)
+
+        return state.copy(
+            codeBlocks = newCodeBlocks,
+            onboardingState = onboardingState
+        ) to actions + onboardingActions
     }
 
     private fun handleDecreaseIndentLevelButtonClicked(
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt
index 60fab28ce..8379c57b8 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt
@@ -84,7 +84,7 @@ object StepQuizCodeBlanksViewStateMapper {
                 null -> false
             }
 
-        val isSpaceButtonHidden = if (state.codeBlanksOperationsSuggestions.isNotEmpty()) {
+        val isSpaceButtonHidden = if (state.operationsSuggestions.isNotEmpty()) {
             when (activeCodeBlock) {
                 is CodeBlock.Print,
                 is CodeBlock.IfStatement,
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
index 23f426c7c..23c0a6af4 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt
@@ -16,8 +16,9 @@ sealed interface StepQuizCodeBlanksViewState {
     ) : StepQuizCodeBlanksViewState {
         val isActionButtonsHidden: Boolean
             get() = when (onboardingState) {
-                is OnboardingState.PrintSuggestionAndCallToAction -> true
+                is OnboardingState.FirstProgram -> true
                 OnboardingState.HighlightDeleteButton,
+                OnboardingState.HighlightEnterButton,
                 OnboardingState.HighlightSpaceButton,
                 OnboardingState.Unavailable -> false
             }
@@ -25,11 +26,14 @@ sealed interface StepQuizCodeBlanksViewState {
         val isDeleteButtonHighlightEffectActive: Boolean
             get() = isDeleteButtonEnabled && onboardingState == OnboardingState.HighlightDeleteButton
 
+        val isEnterButtonHighlightEffectActive: Boolean
+            get() = onboardingState == OnboardingState.HighlightEnterButton
+
         val isSpaceButtonHighlightEffectActive: Boolean
             get() = !isSpaceButtonHidden && onboardingState == OnboardingState.HighlightSpaceButton
 
         val isSuggestionsHighlightEffectActive: Boolean
-            get() = onboardingState == OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+            get() = onboardingState == OnboardingState.FirstProgram.HighlightSuggestions
     }
 
     sealed interface CodeBlockItem {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
index ccc3b5011..ec2d1e7bf 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt
@@ -9,6 +9,7 @@ import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild
 import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState
+import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksOnboardingReducer
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer
 import org.hyperskill.step.domain.model.stub
 
@@ -28,8 +29,8 @@ class StepQuizCodeBlanksReducerOnboardingTest {
     }
 
     @Test
-    fun `Onboarding should be available for print suggestion`() {
-        setOf(47329L, 50968L).forEach { stepId ->
+    fun `Onboarding should be available for first program`() {
+        StepQuizCodeBlanksOnboardingReducer.Companion.FIRST_PROGRAM_STEP_IDS.forEach { stepId ->
             val initialState = StepQuizCodeBlanksFeature.State.Idle
             val (state, _) = reducer.reduce(
                 initialState,
@@ -37,7 +38,7 @@ class StepQuizCodeBlanksReducerOnboardingTest {
             )
 
             assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
-            assertTrue(state.onboardingState is OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions)
+            assertTrue(state.onboardingState is OnboardingState.FirstProgram.HighlightSuggestions)
         }
     }
 
@@ -46,19 +47,37 @@ class StepQuizCodeBlanksReducerOnboardingTest {
         val initialState = StepQuizCodeBlanksFeature.State.Idle
         val (state, _) = reducer.reduce(
             initialState,
-            StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 50969L))
+            StepQuizCodeBlanksFeature.InternalMessage.Initialize(
+                Step.stub(id = StepQuizCodeBlanksOnboardingReducer.Companion.DELETE_BUTTON_STEP_ID)
+            )
         )
 
         assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
         assertTrue(state.onboardingState is OnboardingState.HighlightDeleteButton)
     }
 
+    @Test
+    fun `Onboarding should be available for enter button`() {
+        val initialState = StepQuizCodeBlanksFeature.State.Idle
+        val (state, _) = reducer.reduce(
+            initialState,
+            StepQuizCodeBlanksFeature.InternalMessage.Initialize(
+                Step.stub(id = StepQuizCodeBlanksOnboardingReducer.Companion.ENTER_BUTTON_STEP_ID)
+            )
+        )
+
+        assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
+        assertTrue(state.onboardingState is OnboardingState.HighlightEnterButton)
+    }
+
     @Test
     fun `Onboarding should be available for space button`() {
         val initialState = StepQuizCodeBlanksFeature.State.Idle
         val (state, _) = reducer.reduce(
             initialState,
-            StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 50970L))
+            StepQuizCodeBlanksFeature.InternalMessage.Initialize(
+                Step.stub(id = StepQuizCodeBlanksOnboardingReducer.Companion.SPACE_BUTTON_STEP_ID)
+            )
         )
 
         assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
@@ -80,13 +99,13 @@ class StepQuizCodeBlanksReducerOnboardingTest {
                     )
                 )
             ),
-            onboardingState = OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+            onboardingState = OnboardingState.FirstProgram.HighlightSuggestions
         )
 
         val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion)
         val (state, _) = reducer.reduce(initialState, message)
 
         assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
-        assertEquals(OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton, state.onboardingState)
+        assertEquals(OnboardingState.FirstProgram.HighlightCallToActionButton, state.onboardingState)
     }
 }
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
index 232d64527..a5dd26733 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt
@@ -203,7 +203,7 @@ class StepQuizCodeBlanksReducerSpaceButtonClickedTest {
                         ),
                         CodeBlockChild.SelectSuggestion(
                             isActive = true,
-                            suggestions = initialState.codeBlanksOperationsSuggestions,
+                            suggestions = initialState.operationsSuggestions,
                             selectedSuggestion = null
                         )
                     )
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
index a71716ffd..3cf85df5e 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt
@@ -79,7 +79,7 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest {
                     children = listOf(
                         CodeBlockChild.SelectSuggestion(
                             isActive = true,
-                            suggestions = initialState.codeBlanksStringsSuggestions,
+                            suggestions = initialState.stringsSuggestions,
                             selectedSuggestion = null
                         )
                     )
@@ -111,12 +111,12 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest {
                     children = listOf(
                         CodeBlockChild.SelectSuggestion(
                             isActive = true,
-                            suggestions = initialState.codeBlanksVariablesSuggestions,
+                            suggestions = initialState.variablesSuggestions,
                             selectedSuggestion = null
                         ),
                         CodeBlockChild.SelectSuggestion(
                             isActive = false,
-                            suggestions = initialState.codeBlanksStringsSuggestions,
+                            suggestions = initialState.stringsSuggestions,
                             selectedSuggestion = null
                         )
                     )
@@ -148,7 +148,7 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest {
                     children = listOf(
                         CodeBlockChild.SelectSuggestion(
                             isActive = true,
-                            suggestions = initialState.codeBlanksVariablesAndStringsSuggestions,
+                            suggestions = initialState.variablesAndStringsSuggestions,
                             selectedSuggestion = null
                         )
                     )
@@ -180,7 +180,7 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest {
                     children = listOf(
                         CodeBlockChild.SelectSuggestion(
                             isActive = true,
-                            suggestions = initialState.codeBlanksVariablesAndStringsSuggestions,
+                            suggestions = initialState.variablesAndStringsSuggestions,
                             selectedSuggestion = null
                         )
                     )
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
index 2a9f541c8..48e5bfa0e 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt
@@ -8,10 +8,10 @@ import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksVie
 
 class StepQuizCodeBlanksViewStateTest {
     @Test
-    fun `isActionButtonsHidden should be true when onboarding PrintSuggestionAndCallToAction`() {
+    fun `isActionButtonsHidden should be true when onboarding FirstProgram`() {
         listOf(
-            OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions,
-            OnboardingState.PrintSuggestionAndCallToAction.HighlightCallToActionButton
+            OnboardingState.FirstProgram.HighlightSuggestions,
+            OnboardingState.FirstProgram.HighlightCallToActionButton
         ).forEach { onboardingState ->
             val viewState = stubContentViewState(onboardingState = onboardingState)
             assertTrue(viewState.isActionButtonsHidden)
@@ -50,6 +50,14 @@ class StepQuizCodeBlanksViewStateTest {
         assertFalse(viewState.isDeleteButtonHighlightEffectActive)
     }
 
+    @Test
+    fun `isEnterButtonHighlightEffectActive should be true when onboardingState is HighlightEnterButton`() {
+        val viewState = stubContentViewState(
+            onboardingState = OnboardingState.HighlightEnterButton
+        )
+        assertTrue(viewState.isEnterButtonHighlightEffectActive)
+    }
+
     /* ktlint-disable */
     @Test
     fun `isSpaceButtonHighlightEffectActive should be true when onboardingState is HighlightSpaceButton and isSpaceButtonHidden is false`() {
@@ -69,10 +77,11 @@ class StepQuizCodeBlanksViewStateTest {
         assertFalse(viewState.isSpaceButtonHighlightEffectActive)
     }
 
+    /* ktlint-disable */
     @Test
-    fun `isSuggestionsHighlightEffectActive should be true when onboardingState is HighlightSuggestions`() {
+    fun `isSuggestionsHighlightEffectActive should be true when onboardingState is FirstProgram_HighlightSuggestions`() {
         val viewState = stubContentViewState(
-            onboardingState = OnboardingState.PrintSuggestionAndCallToAction.HighlightSuggestions
+            onboardingState = OnboardingState.FirstProgram.HighlightSuggestions
         )
         assertTrue(viewState.isSuggestionsHighlightEffectActive)
     }

From 9af42c7c4df5dc5156cda3731480db5214614354 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Thu, 19 Sep 2024 03:12:41 +0000
Subject: [PATCH 14/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 2a567f6ff..21f64cdaf 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '553'
\ No newline at end of file
+versionCode = '554'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index d506533a4..d7ee6bd33 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>582</string>
+	<string>583</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 89d181b46..442c35e67 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 582;
+				CURRENT_PROJECT_VERSION = 583;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 88a84db3f..53f817439 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>582</string>
+	<string>583</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index eb2e760b6..b5c70a5f2 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>582</string>
+	<string>583</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 129c4dbe9..44757b320 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>582</string>
+	<string>583</string>
 </dict>
 </plist>

From 398f6b7fdfdd55dfb27e704e09346fe5537b29fc Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Fri, 20 Sep 2024 13:33:45 +0900
Subject: [PATCH 15/27] Shared, iOS: Code blanks support
 code_blanks_available_conditions (#1186)

^ALTAPPS-1346
---
 .../Shared/Model/BlockOptionsExtensions.swift |  2 +
 .../hyperskill/app/step/domain/model/Block.kt |  2 +
 .../template/CodeBlanksTemplateMapper.kt      |  6 ++-
 .../presentation/StepQuizCodeBlanksReducer.kt | 15 ++++--
 .../StepQuizCodeBlanksResolver.kt             | 36 ++++++++++++---
 .../StepQuizCodeBlanksStateExtensions.kt      |  3 ++
 ...seStatementsSuggestionsAvailabilityTest.kt | 37 ++++++++++++---
 ...StepQuizCodeBlanksReducerInitializeTest.kt | 46 ++++++++++++-------
 8 files changed, 112 insertions(+), 35 deletions(-)

diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
index 8b4aaa2d5..eacf1f3ae 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift
@@ -14,6 +14,7 @@ extension Block.Options {
         codeBlanksStrings: [String]? = nil,
         codeBlanksVariables: [String]? = nil,
         codeBlanksOperations: [String]? = nil,
+        codeBlanksAvailableConditions: Set<String>? = nil,
         codeBlanksEnabled: Bool? = nil,
         codeBlanksTemplateString: String? = nil
     ) {
@@ -28,6 +29,7 @@ extension Block.Options {
             codeBlanksStrings: codeBlanksStrings,
             codeBlanksVariables: codeBlanksVariables,
             codeBlanksOperations: codeBlanksOperations,
+            codeBlanksAvailableConditions: codeBlanksAvailableConditions,
             codeBlanksEnabled: codeBlanksEnabled.flatMap(KotlinBoolean.init(value:)),
             codeBlanksTemplateString: codeBlanksTemplateString
         )
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
index 57d2b5fc0..db254cd03 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step/domain/model/Block.kt
@@ -38,6 +38,8 @@ data class Block(
         val codeBlanksVariables: List<String>? = null,
         @SerialName("code_blanks_operations")
         val codeBlanksOperations: List<String>? = null,
+        @SerialName("code_blanks_available_conditions")
+        val codeBlanksAvailableConditions: Set<String>? = null,
         @SerialName("code_blanks_enabled")
         val codeBlanksEnabled: Boolean? = null,
         @SerialName("code_blanks_template")
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
index eef0e448c..ec0d157dd 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
@@ -50,7 +50,8 @@ internal object CodeBlanksTemplateMapper {
                     indentLevel = entry.indentLevel,
                     isDeleteForbidden = entry.isDeleteForbidden,
                     suggestions = StepQuizCodeBlanksResolver.getSuggestionsForBlankCodeBlock(
-                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step)
+                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step),
+                        availableConditions = step.block.options.codeBlanksAvailableConditions ?: emptySet()
                     )
                 )
             CodeBlockTemplateEntryType.PRINT ->
@@ -194,7 +195,8 @@ internal object CodeBlanksTemplateMapper {
                 indentLevel = 0,
                 isDeleteForbidden = false,
                 suggestions = StepQuizCodeBlanksResolver.getSuggestionsForBlankCodeBlock(
-                    isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step)
+                    isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step),
+                    availableConditions = step.block.options.codeBlanksAvailableConditions ?: emptySet()
                 )
             )
         )
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
index ce3e9db99..2a4ef46c6 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt
@@ -200,7 +200,8 @@ class StepQuizCodeBlanksReducer(
                             index = blankInsertIndex,
                             indentLevel = blankIndentLevel,
                             codeBlocks = this,
-                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable
+                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable,
+                            availableConditions = state.availableConditions
                         )
                     )
                 )
@@ -357,7 +358,8 @@ class StepQuizCodeBlanksReducer(
                             index = activeCodeBlockIndex,
                             indentLevel = activeCodeBlock.indentLevel,
                             codeBlocks = this,
-                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable
+                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable,
+                            availableConditions = state.availableConditions
                         )
                     )
                 )
@@ -564,7 +566,8 @@ class StepQuizCodeBlanksReducer(
                             index = insertIndex,
                             indentLevel = indentLevel,
                             codeBlocks = this,
-                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable
+                            isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable,
+                            availableConditions = state.availableConditions
                         )
                     )
                 )
@@ -699,7 +702,8 @@ class StepQuizCodeBlanksReducer(
                                 index = activeCodeBlockIndex,
                                 indentLevel = newIndentLevel,
                                 codeBlocks = this,
-                                isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable
+                                isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable,
+                                availableConditions = state.availableConditions
                             )
                         )
                         else -> activeCodeBlock.updatedIndentLevel(newIndentLevel)
@@ -749,7 +753,8 @@ class StepQuizCodeBlanksReducer(
                     isActive = true,
                     indentLevel = 0,
                     suggestions = StepQuizCodeBlanksResolver.getSuggestionsForBlankCodeBlock(
-                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step)
+                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step),
+                        availableConditions = step.block.options.codeBlanksAvailableConditions ?: emptySet()
                     )
                 )
             )
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
index 670cb4732..ecc0a2a8b 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
@@ -15,14 +15,33 @@ internal object StepQuizCodeBlanksResolver {
         index: Int = -1,
         indentLevel: Int = 0,
         codeBlocks: List<CodeBlock> = emptyList(),
-        isVariableSuggestionAvailable: Boolean
+        isVariableSuggestionAvailable: Boolean,
+        availableConditions: Set<String>
     ): List<Suggestion> =
         when {
-            areElifAndElseStatementsSuggestionsAvailable(index, indentLevel, codeBlocks) ->
-                listOf(Suggestion.Print, Suggestion.Variable, Suggestion.ElifStatement, Suggestion.ElseStatement)
+            areElifAndElseStatementsSuggestionsAvailable(index, indentLevel, codeBlocks, availableConditions) ->
+                buildList {
+                    add(Suggestion.Print)
+                    add(Suggestion.Variable)
+
+                    if (availableConditions.contains(Suggestion.ElifStatement.text)) {
+                        add(Suggestion.ElifStatement)
+                    }
+
+                    if (availableConditions.contains(Suggestion.ElseStatement.text)) {
+                        add(Suggestion.ElseStatement)
+                    }
+                }
 
             isVariableSuggestionAvailable ->
-                listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement)
+                buildList {
+                    add(Suggestion.Print)
+                    add(Suggestion.Variable)
+
+                    if (availableConditions.contains(Suggestion.IfStatement.text)) {
+                        add(Suggestion.IfStatement)
+                    }
+                }
 
             else ->
                 listOf(Suggestion.Print)
@@ -31,9 +50,14 @@ internal object StepQuizCodeBlanksResolver {
     fun areElifAndElseStatementsSuggestionsAvailable(
         index: Int,
         indentLevel: Int,
-        codeBlocks: List<CodeBlock>
+        codeBlocks: List<CodeBlock>,
+        availableConditions: Set<String>
     ): Boolean {
-        if (index < MINIMUM_POSSIBLE_INDEX_FOR_ELIF_AND_ELSE_STATEMENTS || codeBlocks.isEmpty()) {
+        if (
+            index < MINIMUM_POSSIBLE_INDEX_FOR_ELIF_AND_ELSE_STATEMENTS ||
+            codeBlocks.isEmpty() ||
+            availableConditions.isEmpty()
+        ) {
             return false
         }
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensions.kt
index a55f0f21c..d6b5bf2eb 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensions.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensions.kt
@@ -13,6 +13,9 @@ internal val StepQuizCodeBlanksFeature.State.isVariableSuggestionsAvailable: Boo
         StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(it)
     } ?: false
 
+internal val StepQuizCodeBlanksFeature.State.Content.availableConditions: Set<String>
+    get() = step.block.options.codeBlanksAvailableConditions ?: emptySet()
+
 fun StepQuizCodeBlanksFeature.State.Content.createReply(): Reply =
     Reply.code(
         code = buildString {
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt
index 726b291af..671fc6501 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt
@@ -7,12 +7,17 @@ import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock
 import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksResolver
 
 class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest {
+    companion object {
+        private val availableConditions = setOf("if", "elif", "else")
+    }
+
     @Test
     fun `Should return false if index is less than 2`() {
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 1,
             indentLevel = 0,
-            codeBlocks = emptyList()
+            codeBlocks = emptyList(),
+            availableConditions = availableConditions
         )
         assertFalse(result)
     }
@@ -22,7 +27,23 @@ class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 2,
             indentLevel = 0,
-            codeBlocks = emptyList()
+            codeBlocks = emptyList(),
+            availableConditions = availableConditions
+        )
+        assertFalse(result)
+    }
+
+    @Test
+    fun `Should return false if availableConditions is empty`() {
+        val codeBlocks = listOf(
+            CodeBlock.IfStatement(indentLevel = 0, children = emptyList()),
+            CodeBlock.Print(indentLevel = 1, children = emptyList())
+        )
+        val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
+            index = 2,
+            indentLevel = 0,
+            codeBlocks = codeBlocks,
+            availableConditions = emptySet()
         )
         assertFalse(result)
     }
@@ -36,7 +57,8 @@ class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 2,
             indentLevel = 1,
-            codeBlocks = codeBlocks
+            codeBlocks = codeBlocks,
+            availableConditions = availableConditions
         )
         assertFalse(result)
     }
@@ -50,7 +72,8 @@ class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 2,
             indentLevel = 0,
-            codeBlocks = codeBlocks
+            codeBlocks = codeBlocks,
+            availableConditions = availableConditions
         )
         assertTrue(result)
     }
@@ -66,7 +89,8 @@ class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 4,
             indentLevel = 1,
-            codeBlocks = codeBlocks
+            codeBlocks = codeBlocks,
+            availableConditions = availableConditions
         )
         assertTrue(result)
     }
@@ -82,7 +106,8 @@ class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest
         val result = StepQuizCodeBlanksResolver.areElifAndElseStatementsSuggestionsAvailable(
             index = 4,
             indentLevel = 0,
-            codeBlocks = codeBlocks
+            codeBlocks = codeBlocks,
+            availableConditions = availableConditions
         )
         assertTrue(result)
     }
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
index 52f60c050..1c48397cb 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt
@@ -15,28 +15,42 @@ class StepQuizCodeBlanksReducerInitializeTest {
     private val reducer = StepQuizCodeBlanksReducer.stub()
 
     @Test
-    fun `Initialize should return Content state with active Blank and Print and Variable and If suggestions`() {
-        val step = Step.stub(
-            id = 1,
-            block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b")))
+    fun `Initialize should return Content state with active Blank and correct suggestions`() {
+        val blockOptions = listOf(
+            Block.Options(codeBlanksVariables = listOf("a", "b")),
+            Block.Options(
+                codeBlanksVariables = listOf("a", "b"),
+                codeBlanksAvailableConditions = setOf("if", "elif", "else")
+            )
+        )
+        val expectedSuggestions = listOf(
+            listOf(Suggestion.Print, Suggestion.Variable),
+            listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement)
         )
 
-        val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step)
-        val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message)
+        blockOptions.forEachIndexed { index, options ->
+            val step = Step.stub(
+                id = 1,
+                block = Block.stub(options = options)
+            )
 
-        val expectedState = StepQuizCodeBlanksFeature.State.Content(
-            step = step,
-            codeBlocks = listOf(
-                CodeBlock.Blank(
-                    isActive = true,
-                    suggestions = listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement)
+            val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step)
+            val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message)
+
+            val expectedState = StepQuizCodeBlanksFeature.State.Content(
+                step = step,
+                codeBlocks = listOf(
+                    CodeBlock.Blank(
+                        isActive = true,
+                        suggestions = expectedSuggestions[index]
+                    )
                 )
             )
-        )
 
-        assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
-        assertEquals(expectedState.codeBlocks, state.codeBlocks)
-        assertTrue(actions.isEmpty())
+            assertTrue(state is StepQuizCodeBlanksFeature.State.Content)
+            assertEquals(expectedState.codeBlocks, state.codeBlocks)
+            assertTrue(actions.isEmpty())
+        }
     }
 
     @Test

From 248607f272d0e02d65283603a6deccf61bd2c0fa Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Fri, 20 Sep 2024 04:34:22 +0000
Subject: [PATCH 16/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 21f64cdaf..9f56fbeda 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '554'
\ No newline at end of file
+versionCode = '555'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index d7ee6bd33..e3ceb76ef 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>583</string>
+	<string>584</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 442c35e67..8e4cf99cb 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5773,7 +5773,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5794,7 +5794,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5815,7 +5815,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5836,7 +5836,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5857,7 +5857,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5886,7 +5886,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6032,7 +6032,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6068,7 +6068,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 583;
+				CURRENT_PROJECT_VERSION = 584;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 53f817439..2ff1f337d 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>583</string>
+	<string>584</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index b5c70a5f2..cfa626a8b 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>583</string>
+	<string>584</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 44757b320..9f8956b40 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>583</string>
+	<string>584</string>
 </dict>
 </plist>

From 782946317c9510dafcf9d26bb5db3823cb27efb4 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Fri, 20 Sep 2024 18:49:49 +0900
Subject: [PATCH 17/27] iOS: code execution (#1184)

^ALTAPPS-1358
---
 .../StepQuizFeedbackBlocksDelegate.kt         | 14 ++--
 .../project.pbxproj                           | 36 +++++++--
 ...tepQuizFeedbackStateHintKsExtensions.swift | 35 +++++++++
 .../Sources/Models/Constants/Strings.swift    |  6 +-
 .../Feedback/StepQuizFeedbackHintView.swift   | 38 +++------
 .../Views/Feedback/StepQuizFeedbackView.swift | 27 +++----
 .../StepQuizRunCodeFeedbackHintView.swift     | 71 +++++++++++++++++
 .../StepQuizSubmissionFeedbackHintView.swift  | 39 ++++++++++
 .../BackgroundProgressView.swift              |  0
 .../LinearGradientProgressView.swift          |  0
 .../LinearIndeterminateProgressView.swift     | 78 +++++++++++++++++++
 .../data/repository/CodeRepositoryImpl.kt     | 15 ----
 .../code/data/source/CodeRemoteDataSource.kt  |  8 --
 .../code/domain/repository/CodeRepository.kt  |  8 --
 .../app/code/injection/CodeDataComponent.kt   |  7 --
 .../code/injection/CodeDataComponentImpl.kt   | 15 ----
 .../hyperskill/app/core/injection/AppGraph.kt |  7 +-
 .../app/core/injection/BaseAppGraph.kt        | 10 +--
 .../data/repository/RunCodeRepositoryImpl.kt  | 15 ++++
 .../data/source/RunCodeRemoteDataSource.kt    |  8 ++
 .../domain/model/RunCodeExecutionResult.kt}   |  4 +-
 .../domain/repository/RunCodeRepository.kt    |  8 ++
 .../injection/RunCodeDataComponent.kt         |  7 ++
 .../injection/RunCodeDataComponentImpl.kt     | 15 ++++
 .../remote/RunCodeRemoteDataSourceImpl.kt}    | 12 +--
 .../remote/model/RunCodeRequest.kt            |  2 +-
 .../remote/model/RunCodeResponse.kt           |  6 +-
 .../injection/StepQuizComponentImpl.kt        |  2 +-
 .../injection/StepQuizFeatureBuilder.kt       |  6 +-
 .../presentation/StepQuizActionDispatcher.kt  | 16 ++--
 .../step_quiz/presentation/StepQuizFeature.kt |  6 +-
 .../step_quiz/presentation/StepQuizReducer.kt |  2 +-
 .../view/mapper/StepQuizFeedbackMapper.kt     | 38 ++++-----
 .../view/model/StepQuizFeedbackState.kt       |  8 +-
 .../moko-resources/base/strings.xml           |  1 +
 .../step_quiz/StepQuizFeedbackMapperTest.kt   | 10 +--
 36 files changed, 408 insertions(+), 172 deletions(-)
 create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizFeedbackStateHintKsExtensions.swift
 create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizRunCodeFeedbackHintView.swift
 create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizSubmissionFeedbackHintView.swift
 rename iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/{ => ProgressView}/BackgroundProgressView.swift (100%)
 rename iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/{ => ProgressView}/LinearGradientProgressView.swift (100%)
 create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearIndeterminateProgressView.swift
 delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
 delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
 delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
 delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
 delete mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/repository/RunCodeRepositoryImpl.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/source/RunCodeRemoteDataSource.kt
 rename shared/src/commonMain/kotlin/org/hyperskill/app/{code/domain/model/CodeExecutionResult.kt => run_code/domain/model/RunCodeExecutionResult.kt} (81%)
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/repository/RunCodeRepository.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponent.kt
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponentImpl.kt
 rename shared/src/commonMain/kotlin/org/hyperskill/app/{code/remote/CodeRemoteDataSourceImpl.kt => run_code/remote/RunCodeRemoteDataSourceImpl.kt} (60%)
 rename shared/src/commonMain/kotlin/org/hyperskill/app/{code => run_code}/remote/model/RunCodeRequest.kt (84%)
 rename shared/src/commonMain/kotlin/org/hyperskill/app/{code => run_code}/remote/model/RunCodeResponse.kt (63%)

diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
index d25984e41..302cf785c 100644
--- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
+++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/delegate/StepQuizFeedbackBlocksDelegate.kt
@@ -128,11 +128,11 @@ class StepQuizFeedbackBlocksDelegate(
         layoutStepQuizFeedbackBlockBinding.stepQuizSubmissionHint.isVisible =
             hint is StepQuizFeedbackState.Hint.FromSubmission
         layoutStepQuizFeedbackBlockBinding.stepQuizCodeExecutionHint.isVisible =
-            hint is StepQuizFeedbackState.Hint.FromCodeExecution
+            hint is StepQuizFeedbackState.Hint.FromRunCodeExecution
         when (hint) {
             is StepQuizFeedbackState.Hint.FromSubmission ->
                 setRemoteHint(hint, layoutStepQuizFeedbackBlockBinding)
-            is StepQuizFeedbackState.Hint.FromCodeExecution ->
+            is StepQuizFeedbackState.Hint.FromRunCodeExecution ->
                 setCodeExecutionHint(hint, layoutStepQuizFeedbackBlockBinding)
             null -> {
                 // no op
@@ -153,26 +153,26 @@ class StepQuizFeedbackBlocksDelegate(
     }
 
     private fun setCodeExecutionHint(
-        hint: StepQuizFeedbackState.Hint.FromCodeExecution,
+        hint: StepQuizFeedbackState.Hint.FromRunCodeExecution,
         layoutStepQuizFeedbackBlockBinding: LayoutStepQuizFeedbackBlockBinding
     ) {
         with(layoutStepQuizFeedbackBlockBinding) {
             val isInputVisible =
-                hint is StepQuizFeedbackState.Hint.FromCodeExecution.Result && hint.input != null
+                hint is StepQuizFeedbackState.Hint.FromRunCodeExecution.Result && hint.input != null
             stepQuizCodeExecutionInputTitleTextView.isVisible = isInputVisible
             stepQuizCodeExecutionInputValueTextView.isVisible = isInputVisible
             if (isInputVisible) {
                 stepQuizCodeExecutionInputValueTextView.text =
-                    (hint as? StepQuizFeedbackState.Hint.FromCodeExecution.Result)?.input
+                    (hint as? StepQuizFeedbackState.Hint.FromRunCodeExecution.Result)?.input
             }
 
             val isOutputVisible =
-                hint is StepQuizFeedbackState.Hint.FromCodeExecution.Result
+                hint is StepQuizFeedbackState.Hint.FromRunCodeExecution.Result
             stepQuizCodeExecutionOutputTitleTextView.isVisible = isOutputVisible
             stepQuizCodeExecutionOutputValueTextView.isVisible = isOutputVisible
             if (isOutputVisible) {
                 stepQuizCodeExecutionOutputValueTextView.text =
-                    (hint as? StepQuizFeedbackState.Hint.FromCodeExecution.Result)?.output
+                    (hint as? StepQuizFeedbackState.Hint.FromRunCodeExecution.Result)?.output
             }
         }
     }
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index aa2b33102..6620e3d9d 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -288,6 +288,7 @@
 		2C7994AD2A12940D00874C16 /* TrackSelectionListGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7994AC2A12940D00874C16 /* TrackSelectionListGridView.swift */; };
 		2C7994AF2A1299B800874C16 /* TrackSelectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7994AE2A1299B800874C16 /* TrackSelectionListView.swift */; };
 		2C7994B12A129D6100874C16 /* TrackSelectionListSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7994B02A129D6100874C16 /* TrackSelectionListSkeletonView.swift */; };
+		2C79C9CD2C9BDFDD00FF6D21 /* LinearIndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C79C9CC2C9BDFDD00FF6D21 /* LinearIndeterminateProgressView.swift */; };
 		2C7A1B1F2922EB070018D72C /* Hyperskill-Mobile_shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7A1B1E2922EB070018D72C /* Hyperskill-Mobile_shared.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		2C7C0D632B6B45A20093609D /* PaywallFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */; };
 		2C7CB66B2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CB66A2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift */; };
@@ -422,7 +423,7 @@
 		2CA5F8EE2994C3D00013B854 /* DebugViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA5F8ED2994C3D00013B854 /* DebugViewModel.swift */; };
 		2CA5F8F02994C4A90013B854 /* DebugAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA5F8EF2994C4A90013B854 /* DebugAssembly.swift */; };
 		2CA5F8F32994CB870013B854 /* DebugStepNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA5F8F22994CB870013B854 /* DebugStepNavigationView.swift */; };
-		2CA7614B2926272500987B66 /* StepQuizFeedbackHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA7614A2926272500987B66 /* StepQuizFeedbackHintView.swift */; };
+		2CA7614B2926272500987B66 /* StepQuizSubmissionFeedbackHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA7614A2926272500987B66 /* StepQuizSubmissionFeedbackHintView.swift */; };
 		2CA7B88F2893295A00A789EF /* CodeEditorSuggestionsPresentationContextProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA7B88E2893295A00A789EF /* CodeEditorSuggestionsPresentationContextProviding.swift */; };
 		2CA7B892289329C600A789EF /* UIView+FindViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA7B891289329C600A789EF /* UIView+FindViewController.swift */; };
 		2CA7B89428932EB100A789EF /* UIWindowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA7B89328932EB100A789EF /* UIWindowExtensions.swift */; };
@@ -500,6 +501,9 @@
 		2CCA0DED2C857F51007E50A9 /* StudyPlanSectionNextPageLoadingStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA0DEC2C857F51007E50A9 /* StudyPlanSectionNextPageLoadingStateView.swift */; };
 		2CCA0DEF2C858110007E50A9 /* StudyPlanSectionCompletedPageLoadingStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA0DEE2C858110007E50A9 /* StudyPlanSectionCompletedPageLoadingStateView.swift */; };
 		2CCAAB7F2BEA561C001A040F /* spacebot-progress-bar-wow.lottie in Resources */ = {isa = PBXBuildFile; fileRef = 2CCAAB7E2BEA561C001A040F /* spacebot-progress-bar-wow.lottie */; };
+		2CCC1F682C9ADD7500530522 /* StepQuizRunCodeFeedbackHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC1F672C9ADD7500530522 /* StepQuizRunCodeFeedbackHintView.swift */; };
+		2CCC1F6A2C9ADDBD00530522 /* StepQuizFeedbackHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC1F692C9ADDBD00530522 /* StepQuizFeedbackHintView.swift */; };
+		2CCC1F6C2C9AE0B900530522 /* StepQuizFeedbackStateHintKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC1F6B2C9AE0B900530522 /* StepQuizFeedbackStateHintKsExtensions.swift */; };
 		2CCC421F2ABD810A0067C869 /* GhostButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC421E2ABD810A0067C869 /* GhostButtonStyle.swift */; };
 		2CCCA3992862D58E00D98089 /* StepQuizStringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCCA3982862D58E00D98089 /* StepQuizStringView.swift */; };
 		2CCCA39B2862E3BB00D98089 /* StepQuizStringDataType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCCA39A2862E3BB00D98089 /* StepQuizStringDataType.swift */; };
@@ -1095,6 +1099,7 @@
 		2C7994AC2A12940D00874C16 /* TrackSelectionListGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSelectionListGridView.swift; sourceTree = "<group>"; };
 		2C7994AE2A1299B800874C16 /* TrackSelectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSelectionListView.swift; sourceTree = "<group>"; };
 		2C7994B02A129D6100874C16 /* TrackSelectionListSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSelectionListSkeletonView.swift; sourceTree = "<group>"; };
+		2C79C9CC2C9BDFDD00FF6D21 /* LinearIndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearIndeterminateProgressView.swift; sourceTree = "<group>"; };
 		2C7A1B1E2922EB070018D72C /* Hyperskill-Mobile_shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Hyperskill-Mobile_shared.swift"; sourceTree = "<group>"; };
 		2C7C0D622B6B45A20093609D /* PaywallFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallFeaturesView.swift; sourceTree = "<group>"; };
 		2C7CB66A2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFillBlanksAssembly.swift; sourceTree = "<group>"; };
@@ -1230,7 +1235,7 @@
 		2CA5F8ED2994C3D00013B854 /* DebugViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewModel.swift; sourceTree = "<group>"; };
 		2CA5F8EF2994C4A90013B854 /* DebugAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugAssembly.swift; sourceTree = "<group>"; };
 		2CA5F8F22994CB870013B854 /* DebugStepNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugStepNavigationView.swift; sourceTree = "<group>"; };
-		2CA7614A2926272500987B66 /* StepQuizFeedbackHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFeedbackHintView.swift; sourceTree = "<group>"; };
+		2CA7614A2926272500987B66 /* StepQuizSubmissionFeedbackHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizSubmissionFeedbackHintView.swift; sourceTree = "<group>"; };
 		2CA7B88E2893295A00A789EF /* CodeEditorSuggestionsPresentationContextProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditorSuggestionsPresentationContextProviding.swift; sourceTree = "<group>"; };
 		2CA7B891289329C600A789EF /* UIView+FindViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+FindViewController.swift"; sourceTree = "<group>"; };
 		2CA7B89328932EB100A789EF /* UIWindowExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExtensions.swift; sourceTree = "<group>"; };
@@ -1308,6 +1313,9 @@
 		2CCA0DEC2C857F51007E50A9 /* StudyPlanSectionNextPageLoadingStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyPlanSectionNextPageLoadingStateView.swift; sourceTree = "<group>"; };
 		2CCA0DEE2C858110007E50A9 /* StudyPlanSectionCompletedPageLoadingStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyPlanSectionCompletedPageLoadingStateView.swift; sourceTree = "<group>"; };
 		2CCAAB7E2BEA561C001A040F /* spacebot-progress-bar-wow.lottie */ = {isa = PBXFileReference; lastKnownFileType = file; path = "spacebot-progress-bar-wow.lottie"; sourceTree = "<group>"; };
+		2CCC1F672C9ADD7500530522 /* StepQuizRunCodeFeedbackHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizRunCodeFeedbackHintView.swift; sourceTree = "<group>"; };
+		2CCC1F692C9ADDBD00530522 /* StepQuizFeedbackHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFeedbackHintView.swift; sourceTree = "<group>"; };
+		2CCC1F6B2C9AE0B900530522 /* StepQuizFeedbackStateHintKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizFeedbackStateHintKsExtensions.swift; sourceTree = "<group>"; };
 		2CCC421E2ABD810A0067C869 /* GhostButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostButtonStyle.swift; sourceTree = "<group>"; };
 		2CCCA3982862D58E00D98089 /* StepQuizStringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizStringView.swift; sourceTree = "<group>"; };
 		2CCCA39A2862E3BB00D98089 /* StepQuizStringDataType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizStringDataType.swift; sourceTree = "<group>"; };
@@ -1860,6 +1868,7 @@
 				2CFD7C67292542FB00902748 /* StepFeatureStateKsExtensions.swift */,
 				2C84E70D2C47BB5B002EE787 /* StepQuizCodeBlanksViewStateKsExtensions.swift */,
 				2CFD7C692925447600902748 /* StepQuizFeatureStateKsExtensions.swift */,
+				2CCC1F6B2C9AE0B900530522 /* StepQuizFeedbackStateHintKsExtensions.swift */,
 				2CF865782C5A2CA10076736C /* StepQuizFeedbackStateKsExtensions.swift */,
 				2CB9537D2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift */,
 				E9AD65A9292DC0BE00E574F0 /* TopicsRepetitionsFeatureStateKsExtensions.swift */,
@@ -2868,6 +2877,16 @@
 			path = Model;
 			sourceTree = "<group>";
 		};
+		2C79C9CB2C9BDFD200FF6D21 /* ProgressView */ = {
+			isa = PBXGroup;
+			children = (
+				2C95BAD82A4AED4300371C17 /* BackgroundProgressView.swift */,
+				E9D537D12A71330A00F21828 /* LinearGradientProgressView.swift */,
+				2C79C9CC2C9BDFDD00FF6D21 /* LinearIndeterminateProgressView.swift */,
+			);
+			path = ProgressView;
+			sourceTree = "<group>";
+		};
 		2C7A1B1D2922EADC0018D72C /* sharedSwift */ = {
 			isa = PBXGroup;
 			children = (
@@ -3602,13 +3621,11 @@
 		2CBD191B291D3A2500F5FB0B /* SwiftUI */ = {
 			isa = PBXGroup;
 			children = (
-				2C95BAD82A4AED4300371C17 /* BackgroundProgressView.swift */,
 				2C725B622809198000A49043 /* BackgroundView.swift */,
 				2C54E4272A1F717F003406B9 /* CardView.swift */,
 				E91017142832975C002E70F5 /* CheckboxButton.swift */,
 				2CEB50D5288ACBD10044F9AB /* HSTabBar.swift */,
 				2C43CDF828B55CC600E74762 /* HyperskillLogoView.swift */,
-				E9D537D12A71330A00F21828 /* LinearGradientProgressView.swift */,
 				2C32375228380C340062CAF6 /* NavigationToolbarInfoItem.swift */,
 				2C1B71042B6CB7D9003FD4A1 /* OffsetObservingScrollView.swift */,
 				E9C3506E2886D0600080D277 /* OpenURLInsideAppButton.swift */,
@@ -3625,6 +3642,7 @@
 				2CF87DA029B717E20092FF83 /* Introspect */,
 				2C677CF92C4A2CA50019AF03 /* Layouts */,
 				2C82BA302844AFED004C9013 /* PlaceholderView */,
+				2C79C9CB2C9BDFD200FF6D21 /* ProgressView */,
 				2C60F1B428880AA600B66C78 /* Skeletons */,
 				2CA8E08E281039EB00154088 /* Styles */,
 				2C9C89392C2A713800C41890 /* TextEffects */,
@@ -4218,10 +4236,12 @@
 		2CF865772C5A25E40076736C /* Feedback */ = {
 			isa = PBXGroup;
 			children = (
-				2CA7614A2926272500987B66 /* StepQuizFeedbackHintView.swift */,
+				2CCC1F692C9ADDBD00530522 /* StepQuizFeedbackHintView.swift */,
 				2CF865752C5A25D60076736C /* StepQuizFeedbackStatusView.swift */,
 				2CF865732C5A1EE70076736C /* StepQuizFeedbackView.swift */,
 				2C5A57E82C5B1EF2001DA85A /* StepQuizFeedbackWrongStateView.swift */,
+				2CCC1F672C9ADD7500530522 /* StepQuizRunCodeFeedbackHintView.swift */,
+				2CA7614A2926272500987B66 /* StepQuizSubmissionFeedbackHintView.swift */,
 			);
 			path = Feedback;
 			sourceTree = "<group>";
@@ -5085,6 +5105,7 @@
 				E9950E9328893F1700C4D962 /* ProfileDailyStudyRemindersView.swift in Sources */,
 				2C8E66D52878771B00D3928D /* ProfilePresentationDescription.swift in Sources */,
 				2C5F19152AE667C90039414D /* LeftAlignedCollectionViewFlowLayout.swift in Sources */,
+				2C79C9CD2C9BDFDD00FF6D21 /* LinearIndeterminateProgressView.swift in Sources */,
 				E993E9A928426FF2005988EC /* StepQuizSortingViewData.swift in Sources */,
 				E9F59B90289FE053001CEA02 /* ProfileSettingsViewModel.swift in Sources */,
 				2C8DD40C2AFB7E6A00FD5359 /* ShareStreakModalView.swift in Sources */,
@@ -5342,7 +5363,7 @@
 				2C45E7C52A0FEE7A00DFF32D /* StarRatingView.swift in Sources */,
 				2C9AA3F22C245C5700F5170E /* WelcomeQuestionnaireItemType+ImageResource.swift in Sources */,
 				2C4D43F62BD8C5810058EBBC /* StepTypeWrapper.swift in Sources */,
-				2CA7614B2926272500987B66 /* StepQuizFeedbackHintView.swift in Sources */,
+				2CA7614B2926272500987B66 /* StepQuizSubmissionFeedbackHintView.swift in Sources */,
 				2C58DE292803D197002A2774 /* UIColor+DynamicColor.swift in Sources */,
 				2C2D73442B1736E000CBB1DA /* AppTabItemsAvailabilityService.swift in Sources */,
 				E996D414292228A700A47498 /* TopicsRepetitionsView.swift in Sources */,
@@ -5382,6 +5403,7 @@
 				2C772E7D28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift in Sources */,
 				2CD67CA12C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift in Sources */,
 				2C963BCC2812D9330036DD53 /* ProfileSettingsAssembly.swift in Sources */,
+				2CCC1F6A2C9ADDBD00530522 /* StepQuizFeedbackHintView.swift in Sources */,
 				E9470C6B29810AB7008ACF9A /* StepQuizOutputProtocol.swift in Sources */,
 				2C079687285CFFF500EE0487 /* StepQuizSortingAssembly.swift in Sources */,
 				2C58DE2B2803DEE2002A2774 /* VerticalCenteredScrollView.swift in Sources */,
@@ -5395,6 +5417,7 @@
 				2CDF14D628EF02740060D972 /* StackRouter.swift in Sources */,
 				2C20FBAE284F1D05006D879E /* FontWeightNameMapping.swift in Sources */,
 				2C2D4932281154CB00753F16 /* AppGraph.swift in Sources */,
+				2CCC1F682C9ADD7500530522 /* StepQuizRunCodeFeedbackHintView.swift in Sources */,
 				2CCC421F2ABD810A0067C869 /* GhostButtonStyle.swift in Sources */,
 				E94BB0442A9DEEFC00736B7C /* StepQuizParsonsSkeletonView.swift in Sources */,
 				2CB45764288ED6D4007C2D77 /* StepQuizActionButtonCodeQuizDelegate.swift in Sources */,
@@ -5524,6 +5547,7 @@
 				2C4F63A12A102D3300D4EE39 /* SharedProjectLevelWrapper.swift in Sources */,
 				E97EDB002A8F595E00CABF8E /* BadgeEarnedModalViewController.swift in Sources */,
 				2CD48D8B2858684100CFCC4A /* StepQuizViewData.swift in Sources */,
+				2CCC1F6C2C9AE0B900530522 /* StepQuizFeedbackStateHintKsExtensions.swift in Sources */,
 				2CB0ADF52B04BC8E0089D557 /* ChallengeWidgetAssembly.swift in Sources */,
 				2C05AC572A0EC9E50039C7EF /* ProjectSelectionListHeaderSkeletonView.swift in Sources */,
 				2CC63AEC2B70B25200407810 /* ProfileSettingsSubscriptionSectionView.swift in Sources */,
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizFeedbackStateHintKsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizFeedbackStateHintKsExtensions.swift
new file mode 100644
index 000000000..3e66a3ca8
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/sharedSwift/Extensions/StepQuizFeedbackStateHintKsExtensions.swift
@@ -0,0 +1,35 @@
+import Foundation
+import shared
+
+extension StepQuizFeedbackStateHintKs: Equatable {
+    public static func == (lhs: StepQuizFeedbackStateHintKs, rhs: StepQuizFeedbackStateHintKs) -> Bool {
+        switch(lhs, rhs) {
+        case (.fromSubmission(let lhsData), .fromSubmission(let rhsData)):
+            lhsData.isEqual(rhsData)
+        case (.fromRunCodeExecution(let lhs), .fromRunCodeExecution(let rhs)):
+            StepQuizFeedbackStateHintFromRunCodeExecutionKs(lhs) == StepQuizFeedbackStateHintFromRunCodeExecutionKs(rhs)
+        case (.fromRunCodeExecution, .fromSubmission):
+            false
+        case (.fromSubmission, .fromRunCodeExecution):
+            false
+        }
+    }
+}
+
+extension StepQuizFeedbackStateHintFromRunCodeExecutionKs: Equatable {
+    public static func == (
+        lhs: StepQuizFeedbackStateHintFromRunCodeExecutionKs,
+        rhs: StepQuizFeedbackStateHintFromRunCodeExecutionKs
+    ) -> Bool {
+        switch(lhs, rhs) {
+        case (.loading, .loading):
+            true
+        case (.result(let lhsData), .result(let rhsData)):
+            lhsData.isEqual(rhsData)
+        case (.result, .loading):
+            false
+        case (.loading, .result):
+            false
+        }
+    }
+}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
index 20e8bb922..5b799f3d3 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
@@ -98,7 +98,11 @@ enum Strings {
         static let quizStatusEvaluation = sharedStrings.step_quiz_status_evaluation_text.localized()
         static let quizStatusLoading = sharedStrings.step_quiz_status_loading_text.localized()
 
-        static let feedbackTitle = sharedStrings.step_quiz_feedback_title.localized()
+        static let submissionFeedbackHintTitle = sharedStrings.step_quiz_feedback_title.localized()
+        static let runCodeFeedbackHintTitle = sharedStrings.step_quiz_code_execution_title.localized()
+        static let runCodeFeedbackHintCase1 = sharedStrings.step_quiz_code_execution_test_case_1.localized()
+        static let runCodeFeedbackHintInput = sharedStrings.step_quiz_code_execution_input.localized()
+        static let runCodeFeedbackHintOutput = sharedStrings.step_quiz_code_execution_output.localized()
 
         static let continueButton = sharedStrings.step_quiz_continue_button_text.localized()
         static let retryButton = sharedStrings.step_quiz_retry_button_text.localized()
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackHintView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackHintView.swift
index 93d1f3eac..557cd6791 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackHintView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackHintView.swift
@@ -1,39 +1,23 @@
+import shared
 import SwiftUI
 
 struct StepQuizFeedbackHintView: View {
-    let text: String
+    let hint: StepQuizFeedbackStateHint
 
     var body: some View {
-        VStack(alignment: .leading, spacing: LayoutInsets.smallInset) {
-            Text(Strings.StepQuiz.feedbackTitle)
-                .font(.caption)
-                .foregroundColor(.tertiaryText)
-                .frame(maxWidth: .infinity, alignment: .leading)
-
-            LatexView(
-                text: text,
-                configuration: .quizContent(
-                    textFont: .monospacedSystemFont(ofSize: 14, weight: .regular),
-                    textColor: .primaryText,
-                    backgroundColor: .clear
-                )
+        switch StepQuizFeedbackStateHintKs(hint) {
+        case .fromSubmission(let data):
+            StepQuizSubmissionFeedbackHintView(text: data.text)
+        case .fromRunCodeExecution(let runCodeExecution):
+            StepQuizRunCodeFeedbackHintView(
+                runCodeExecution: StepQuizFeedbackStateHintFromRunCodeExecutionKs(runCodeExecution)
             )
         }
-        .padding()
-        .background(Color.background)
-        .addBorder()
     }
 }
 
-#if DEBUG
-#Preview {
-    ScrollView {
-        StepQuizFeedbackHintView(
-            text: """
-That's right! Since any comparison results in a boolean value, there is no need to write everything twice.
-"""
-        )
+extension StepQuizFeedbackHintView: Equatable {
+    static func == (lhs: StepQuizFeedbackHintView, rhs: StepQuizFeedbackHintView) -> Bool {
+        StepQuizFeedbackStateHintKs(lhs.hint) == StepQuizFeedbackStateHintKs(rhs.hint)
     }
-    .padding()
 }
-#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
index e1f389486..c9fb266e5 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizFeedbackView.swift
@@ -14,13 +14,8 @@ struct StepQuizFeedbackView: View {
             StepQuizFeedbackStatusView(state: .correct)
 
             if let hint = correctState.hint {
-                let hintKs = StepQuizFeedbackStateHintKs(hint)
-                switch hintKs {
-                case .fromCodeExecution:
-                    #warning("TODO: ALTAPPS-1358")
-                case .fromSubmission(let fromSubmission):
-                    StepQuizFeedbackHintView(text: fromSubmission.text)
-                }
+                StepQuizFeedbackHintView(hint: hint)
+                    .equatable()
             }
         case .wrong(let wrongState):
             StepQuizFeedbackWrongStateView(
@@ -28,14 +23,9 @@ struct StepQuizFeedbackView: View {
                 onAction: onAction
             )
 
-            if let feedbackHint = wrongState.hint {
-                let hintKs = StepQuizFeedbackStateHintKs(feedbackHint)
-                switch hintKs {
-                case .fromCodeExecution:
-                    #warning("TODO: ALTAPPS-1358")
-                case .fromSubmission(let fromSubmission):
-                    StepQuizFeedbackHintView(text: fromSubmission.text)
-                }
+            if let hint = wrongState.hint {
+                StepQuizFeedbackHintView(hint: hint)
+                    .equatable()
             }
         case .rejectedSubmission(let rejectedSubmissionState):
             StepQuizFeedbackStatusView(
@@ -43,8 +33,13 @@ struct StepQuizFeedbackView: View {
                     message: rejectedSubmissionState.message
                 )
             )
-        case .evaluation:
+        case .evaluation(let stepQuizFeedbackStateEvaluation):
             StepQuizFeedbackStatusView(state: .evaluation)
+
+            if let hint = stepQuizFeedbackStateEvaluation.hint {
+                StepQuizFeedbackHintView(hint: hint)
+                    .equatable()
+            }
         case .validationFailed(let validationFailedState):
             StepQuizFeedbackStatusView(
                 state: .invalidReply(
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizRunCodeFeedbackHintView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizRunCodeFeedbackHintView.swift
new file mode 100644
index 000000000..a9f6dd51b
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizRunCodeFeedbackHintView.swift
@@ -0,0 +1,71 @@
+import shared
+import SwiftUI
+
+extension StepQuizRunCodeFeedbackHintView {
+    struct Appearance {
+        let spacing = LayoutInsets.defaultInset
+        let interitemSpacing = LayoutInsets.smallInset
+
+        let font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)
+    }
+}
+
+struct StepQuizRunCodeFeedbackHintView: View {
+    private(set) var appearance = Appearance()
+
+    let runCodeExecution: StepQuizFeedbackStateHintFromRunCodeExecutionKs
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: appearance.spacing) {
+            Text(Strings.StepQuiz.runCodeFeedbackHintTitle)
+
+            switch runCodeExecution {
+            case .loading:
+                HStack(spacing: appearance.spacing) {
+                    Text(Strings.StepQuiz.runCodeFeedbackHintCase1)
+                    LinearIndeterminateProgressView()
+                }
+            case .result(let runCodeExecutionResult):
+                Text(Strings.StepQuiz.runCodeFeedbackHintCase1)
+
+                VStack(alignment: .leading, spacing: appearance.interitemSpacing) {
+                    if let input = runCodeExecutionResult.input {
+                        VStack(alignment: .leading, spacing: appearance.interitemSpacing / 2) {
+                            Text(Strings.StepQuiz.runCodeFeedbackHintInput)
+                            Text(input)
+                        }
+                    }
+
+                    VStack(alignment: .leading, spacing: appearance.interitemSpacing / 2) {
+                        Text(Strings.StepQuiz.runCodeFeedbackHintOutput)
+                        Text(runCodeExecutionResult.output)
+                    }
+                }
+            }
+        }
+        .font(Font(appearance.font))
+        .foregroundColor(.primaryText)
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .padding()
+        .background(Color.background)
+        .addBorder()
+    }
+}
+
+#if DEBUG
+ #Preview {
+     VStack {
+         StepQuizRunCodeFeedbackHintView(runCodeExecution: .loading)
+
+         StepQuizRunCodeFeedbackHintView(
+            runCodeExecution: .result(
+                StepQuizFeedbackStateHintFromRunCodeExecutionResult(
+                    input: "5",
+                    output: "120"
+                )
+            )
+         )
+     }
+     .padding()
+ }
+#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizSubmissionFeedbackHintView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizSubmissionFeedbackHintView.swift
new file mode 100644
index 000000000..42210c922
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/Feedback/StepQuizSubmissionFeedbackHintView.swift
@@ -0,0 +1,39 @@
+import SwiftUI
+
+struct StepQuizSubmissionFeedbackHintView: View {
+    let text: String
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: LayoutInsets.smallInset) {
+            Text(Strings.StepQuiz.submissionFeedbackHintTitle)
+                .font(.caption)
+                .foregroundColor(.tertiaryText)
+                .frame(maxWidth: .infinity, alignment: .leading)
+
+            LatexView(
+                text: text,
+                configuration: .quizContent(
+                    textFont: .monospacedSystemFont(ofSize: 14, weight: .regular),
+                    textColor: .primaryText,
+                    backgroundColor: .clear
+                )
+            )
+        }
+        .padding()
+        .background(Color.background)
+        .addBorder()
+    }
+}
+
+#if DEBUG
+#Preview {
+    ScrollView {
+        StepQuizSubmissionFeedbackHintView(
+            text: """
+That's right! Since any comparison results in a boolean value, there is no need to write everything twice.
+"""
+        )
+    }
+    .padding()
+}
+#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BackgroundProgressView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/BackgroundProgressView.swift
similarity index 100%
rename from iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/BackgroundProgressView.swift
rename to iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/BackgroundProgressView.swift
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/LinearGradientProgressView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearGradientProgressView.swift
similarity index 100%
rename from iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/LinearGradientProgressView.swift
rename to iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearGradientProgressView.swift
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearIndeterminateProgressView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearIndeterminateProgressView.swift
new file mode 100644
index 000000000..d0579aad6
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/SwiftUI/ProgressView/LinearIndeterminateProgressView.swift
@@ -0,0 +1,78 @@
+import SwiftUI
+
+extension LinearIndeterminateProgressView {
+    struct Appearance {
+        let trackTintColor = Color(ColorPalette.onSurfaceAlpha9)
+        let progressTintColor = Color.accentColor
+
+        let height: CGFloat = 6
+    }
+}
+
+struct LinearIndeterminateProgressView: View {
+    private(set) var appearance = Appearance()
+
+    @State private var width: CGFloat = 0
+    @State private var offset: CGFloat = 0
+
+    @Environment(\.isEnabled) private var isEnabled
+
+    var body: some View {
+        Rectangle()
+            .foregroundColor(appearance.trackTintColor)
+            .readWidth()
+            .overlay(
+                Rectangle()
+                    .foregroundColor(appearance.progressTintColor)
+                    .frame(width: width * 0.26, height: appearance.height)
+                    .clipShape(Capsule())
+                    .offset(x: -width * 0.6, y: 0)
+                    .offset(x: width * 1.2 * offset, y: 0)
+                    .animation(.default.repeatForever().speed(0.265), value: offset)
+                    .onAppear {
+                        withAnimation {
+                            offset = 1
+                        }
+                    }
+            )
+            .clipShape(Capsule())
+            .opacity(isEnabled ? 1 : 0)
+            .animation(.default, value: isEnabled)
+            .frame(height: appearance.height)
+            .onPreferenceChange(WidthPreferenceKey.self) { width in
+                self.width = width
+            }
+    }
+}
+
+private struct WidthPreferenceKey: PreferenceKey {
+    static var defaultValue: CGFloat = 0
+
+    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
+        value = max(value, nextValue())
+    }
+}
+
+private struct ReadWidthModifier: ViewModifier {
+    private var sizeView: some View {
+        GeometryReader { proxy in
+            Color.clear.preference(key: WidthPreferenceKey.self, value: proxy.size.width)
+        }
+    }
+
+    func body(content: Content) -> some View {
+        content.background(sizeView)
+    }
+}
+
+private extension View {
+    func readWidth() -> some View {
+        modifier(ReadWidthModifier())
+    }
+}
+
+#if DEBUG
+#Preview {
+    LinearIndeterminateProgressView()
+}
+#endif
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
deleted file mode 100644
index c58994d78..000000000
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/repository/CodeRepositoryImpl.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.hyperskill.app.code.data.repository
-
-import org.hyperskill.app.code.data.source.CodeRemoteDataSource
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
-import org.hyperskill.app.code.domain.repository.CodeRepository
-import org.hyperskill.app.code.remote.model.RunCodeRequest
-
-internal class CodeRepositoryImpl(
-    private val codeRemoteDataSource: CodeRemoteDataSource
-) : CodeRepository {
-    override suspend fun runCode(runCodeRequest: RunCodeRequest): Result<CodeExecutionResult> =
-        codeRemoteDataSource
-            .runCode(runCodeRequest)
-            .mapCatching { it.codeExecutionResults.first() }
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
deleted file mode 100644
index cb09df3d8..000000000
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/data/source/CodeRemoteDataSource.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.hyperskill.app.code.data.source
-
-import org.hyperskill.app.code.remote.model.RunCodeRequest
-import org.hyperskill.app.code.remote.model.RunCodeResponse
-
-interface CodeRemoteDataSource {
-    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeResponse>
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
deleted file mode 100644
index 1934e9266..000000000
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/repository/CodeRepository.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.hyperskill.app.code.domain.repository
-
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
-import org.hyperskill.app.code.remote.model.RunCodeRequest
-
-interface CodeRepository {
-    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<CodeExecutionResult>
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
deleted file mode 100644
index 197945cc2..000000000
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponent.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.hyperskill.app.code.injection
-
-import org.hyperskill.app.code.domain.repository.CodeRepository
-
-interface CodeDataComponent {
-    val codeRepository: CodeRepository
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
deleted file mode 100644
index e7fbd5fc1..000000000
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/injection/CodeDataComponentImpl.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.hyperskill.app.code.injection
-
-import org.hyperskill.app.code.data.repository.CodeRepositoryImpl
-import org.hyperskill.app.code.data.source.CodeRemoteDataSource
-import org.hyperskill.app.code.domain.repository.CodeRepository
-import org.hyperskill.app.code.remote.CodeRemoteDataSourceImpl
-import org.hyperskill.app.core.injection.AppGraph
-
-internal class CodeDataComponentImpl(appGraph: AppGraph) : CodeDataComponent {
-
-    private val codeRemoteDataSource: CodeRemoteDataSource =
-        CodeRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient)
-
-    override val codeRepository: CodeRepository = CodeRepositoryImpl(codeRemoteDataSource)
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
index 4356c37f4..8cd7672f2 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
@@ -8,7 +8,6 @@ import org.hyperskill.app.auth.injection.AuthSocialComponent
 import org.hyperskill.app.badges.injection.BadgesDataComponent
 import org.hyperskill.app.challenges.injection.ChallengesDataComponent
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponent
-import org.hyperskill.app.code.injection.CodeDataComponent
 import org.hyperskill.app.comments.injection.CommentsDataComponent
 import org.hyperskill.app.comments.screen.domain.model.CommentsScreenFeatureParams
 import org.hyperskill.app.comments.screen.injection.CommentsScreenComponent
@@ -57,6 +56,7 @@ import org.hyperskill.app.purchases.injection.PurchaseComponent
 import org.hyperskill.app.reactions.injection.ReactionsDataComponent
 import org.hyperskill.app.request_review.injection.RequestReviewDataComponent
 import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponent
+import org.hyperskill.app.run_code.injection.RunCodeDataComponent
 import org.hyperskill.app.search.injection.SearchComponent
 import org.hyperskill.app.search_results.injection.SearchResultsDataComponent
 import org.hyperskill.app.sentry.injection.SentryComponent
@@ -144,6 +144,7 @@ interface AppGraph {
     fun buildStepFeedbackComponent(stepRoute: StepRoute): StepFeedbackComponent
 
     fun buildSubmissionsDataComponent(): SubmissionsDataComponent
+    fun buildRunCodeDataComponent(): RunCodeDataComponent
 
     fun buildStudyPlanWidgetComponent(): StudyPlanWidgetComponent
 
@@ -204,6 +205,7 @@ interface AppGraph {
     fun buildWelcomeOnboardingTrackDetailsComponent(
         track: WelcomeOnboardingTrack
     ): WelcomeOnboardingTrackDetailsComponent
+
     fun buildWelcomeOnboardingFinishComponent(): WelcomeOnboardingFinishComponent
     fun buildRequestReviewDataComponent(): RequestReviewDataComponent
     fun buildRequestReviewModalComponent(stepRoute: StepRoute): RequestReviewModalComponent
@@ -213,11 +215,12 @@ interface AppGraph {
     fun buildProblemsLimitInfoModalComponent(
         params: ProblemsLimitInfoModalFeatureParams
     ): ProblemsLimitInfoModalComponent
+
     fun buildTopicCompletedModalComponent(
         params: TopicCompletedModalFeatureParams
     ): TopicCompletedModalComponent
+
     fun buildCommentsScreenComponent(
         params: CommentsScreenFeatureParams
     ): CommentsScreenComponent
-    fun buildCodeDataComponent(): CodeDataComponent
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
index 50085b798..05915f1fc 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
@@ -14,8 +14,6 @@ import org.hyperskill.app.challenges.injection.ChallengesDataComponent
 import org.hyperskill.app.challenges.injection.ChallengesDataComponentImpl
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponent
 import org.hyperskill.app.challenges.widget.injection.ChallengeWidgetComponentImpl
-import org.hyperskill.app.code.injection.CodeDataComponent
-import org.hyperskill.app.code.injection.CodeDataComponentImpl
 import org.hyperskill.app.comments.injection.CommentsDataComponent
 import org.hyperskill.app.comments.injection.CommentsDataComponentImpl
 import org.hyperskill.app.comments.screen.domain.model.CommentsScreenFeatureParams
@@ -104,6 +102,8 @@ import org.hyperskill.app.request_review.injection.RequestReviewDataComponent
 import org.hyperskill.app.request_review.injection.RequestReviewDataComponentImpl
 import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponent
 import org.hyperskill.app.request_review.modal.injection.RequestReviewModalComponentImpl
+import org.hyperskill.app.run_code.injection.RunCodeDataComponent
+import org.hyperskill.app.run_code.injection.RunCodeDataComponentImpl
 import org.hyperskill.app.search.injection.SearchComponent
 import org.hyperskill.app.search.injection.SearchComponentImpl
 import org.hyperskill.app.search_results.injection.SearchResultsDataComponent
@@ -327,6 +327,9 @@ abstract class BaseAppGraph : AppGraph {
     override fun buildSubmissionsDataComponent(): SubmissionsDataComponent =
         SubmissionsDataComponentImpl(this)
 
+    override fun buildRunCodeDataComponent(): RunCodeDataComponent =
+        RunCodeDataComponentImpl(appGraph = this)
+
     override fun buildTrackDataComponent(): TrackDataComponent =
         TrackDataComponentImpl(this)
 
@@ -558,7 +561,4 @@ abstract class BaseAppGraph : AppGraph {
         params: CommentsScreenFeatureParams
     ): CommentsScreenComponent =
         CommentsScreenComponentImpl(appGraph = this, params = params)
-
-    override fun buildCodeDataComponent(): CodeDataComponent =
-        CodeDataComponentImpl(appGraph = this)
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/repository/RunCodeRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/repository/RunCodeRepositoryImpl.kt
new file mode 100644
index 000000000..332e6bc0f
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/repository/RunCodeRepositoryImpl.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.run_code.data.repository
+
+import org.hyperskill.app.run_code.data.source.RunCodeRemoteDataSource
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
+import org.hyperskill.app.run_code.domain.repository.RunCodeRepository
+import org.hyperskill.app.run_code.remote.model.RunCodeRequest
+
+internal class RunCodeRepositoryImpl(
+    private val runCodeRemoteDataSource: RunCodeRemoteDataSource
+) : RunCodeRepository {
+    override suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeExecutionResult> =
+        runCodeRemoteDataSource
+            .runCode(runCodeRequest)
+            .mapCatching { it.runCodeExecutionResults.first() }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/source/RunCodeRemoteDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/source/RunCodeRemoteDataSource.kt
new file mode 100644
index 000000000..d8d05f9e1
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/data/source/RunCodeRemoteDataSource.kt
@@ -0,0 +1,8 @@
+package org.hyperskill.app.run_code.data.source
+
+import org.hyperskill.app.run_code.remote.model.RunCodeRequest
+import org.hyperskill.app.run_code.remote.model.RunCodeResponse
+
+interface RunCodeRemoteDataSource {
+    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeResponse>
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/model/RunCodeExecutionResult.kt
similarity index 81%
rename from shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt
rename to shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/model/RunCodeExecutionResult.kt
index 290cfe958..a63978c2d 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/domain/model/CodeExecutionResult.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/model/RunCodeExecutionResult.kt
@@ -1,10 +1,10 @@
-package org.hyperskill.app.code.domain.model
+package org.hyperskill.app.run_code.domain.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 
 @Serializable
-data class CodeExecutionResult(
+data class RunCodeExecutionResult(
     @SerialName("language")
     val language: String,
     @SerialName("stdin")
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/repository/RunCodeRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/repository/RunCodeRepository.kt
new file mode 100644
index 000000000..5daa5950e
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/domain/repository/RunCodeRepository.kt
@@ -0,0 +1,8 @@
+package org.hyperskill.app.run_code.domain.repository
+
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
+import org.hyperskill.app.run_code.remote.model.RunCodeRequest
+
+interface RunCodeRepository {
+    suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeExecutionResult>
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponent.kt
new file mode 100644
index 000000000..5ea5bd785
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponent.kt
@@ -0,0 +1,7 @@
+package org.hyperskill.app.run_code.injection
+
+import org.hyperskill.app.run_code.domain.repository.RunCodeRepository
+
+interface RunCodeDataComponent {
+    val runCodeRepository: RunCodeRepository
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponentImpl.kt
new file mode 100644
index 000000000..58c21e4ab
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/injection/RunCodeDataComponentImpl.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.run_code.injection
+
+import org.hyperskill.app.core.injection.AppGraph
+import org.hyperskill.app.run_code.data.repository.RunCodeRepositoryImpl
+import org.hyperskill.app.run_code.data.source.RunCodeRemoteDataSource
+import org.hyperskill.app.run_code.domain.repository.RunCodeRepository
+import org.hyperskill.app.run_code.remote.RunCodeRemoteDataSourceImpl
+
+internal class RunCodeDataComponentImpl(appGraph: AppGraph) : RunCodeDataComponent {
+    private val runCodeRemoteDataSource: RunCodeRemoteDataSource =
+        RunCodeRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient)
+
+    override val runCodeRepository: RunCodeRepository =
+        RunCodeRepositoryImpl(runCodeRemoteDataSource)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/RunCodeRemoteDataSourceImpl.kt
similarity index 60%
rename from shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt
rename to shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/RunCodeRemoteDataSourceImpl.kt
index ef783f4e6..20bbde6fe 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/CodeRemoteDataSourceImpl.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/RunCodeRemoteDataSourceImpl.kt
@@ -1,4 +1,4 @@
-package org.hyperskill.app.code.remote
+package org.hyperskill.app.run_code.remote
 
 import io.ktor.client.HttpClient
 import io.ktor.client.call.body
@@ -6,11 +6,13 @@ import io.ktor.client.request.post
 import io.ktor.client.request.setBody
 import io.ktor.http.ContentType
 import io.ktor.http.contentType
-import org.hyperskill.app.code.data.source.CodeRemoteDataSource
-import org.hyperskill.app.code.remote.model.RunCodeRequest
-import org.hyperskill.app.code.remote.model.RunCodeResponse
+import org.hyperskill.app.run_code.data.source.RunCodeRemoteDataSource
+import org.hyperskill.app.run_code.remote.model.RunCodeRequest
+import org.hyperskill.app.run_code.remote.model.RunCodeResponse
 
-internal class CodeRemoteDataSourceImpl(private val httpClient: HttpClient) : CodeRemoteDataSource {
+internal class RunCodeRemoteDataSourceImpl(
+    private val httpClient: HttpClient
+) : RunCodeRemoteDataSource {
     override suspend fun runCode(runCodeRequest: RunCodeRequest): Result<RunCodeResponse> =
         runCatching {
             httpClient
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeRequest.kt
similarity index 84%
rename from shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt
rename to shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeRequest.kt
index 56b6c2802..cb494658c 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeRequest.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeRequest.kt
@@ -1,4 +1,4 @@
-package org.hyperskill.app.code.remote.model
+package org.hyperskill.app.run_code.remote.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeResponse.kt
similarity index 63%
rename from shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt
rename to shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeResponse.kt
index 582e34c88..4cbedc4c2 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/code/remote/model/RunCodeResponse.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/run_code/remote/model/RunCodeResponse.kt
@@ -1,15 +1,15 @@
-package org.hyperskill.app.code.remote.model
+package org.hyperskill.app.run_code.remote.model
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.core.remote.Meta
 import org.hyperskill.app.core.remote.MetaResponse
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
 
 @Serializable
 data class RunCodeResponse(
     @SerialName("meta")
     override val meta: Meta,
     @SerialName("run-code")
-    val codeExecutionResults: List<CodeExecutionResult>
+    val runCodeExecutionResults: List<RunCodeExecutionResult>
 ) : MetaResponse
\ No newline at end of file
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 d79794efd..ea7a8430e 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
@@ -73,7 +73,7 @@ internal class StepQuizComponentImpl(
             sentryInteractor = appGraph.sentryComponent.sentryInteractor,
             onboardingInteractor = appGraph.buildOnboardingDataComponent().onboardingInteractor,
             featuresDataSource = appGraph.profileDataComponent.featuresDataSource,
-            codeRepository = appGraph.buildCodeDataComponent().codeRepository,
+            runCodeRepository = appGraph.buildRunCodeDataComponent().runCodeRepository,
             logger = appGraph.loggerComponent.logger,
             buildVariant = appGraph.commonComponent.buildKonfig.buildVariant,
             stepQuizHintsActionDispatcher = stepQuizHintsComponent.stepQuizHintsActionDispatcher,
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 46f0f8316..f2244d0a5 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
@@ -3,7 +3,6 @@ package org.hyperskill.app.step_quiz.injection
 import co.touchlab.kermit.Logger
 import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
 import org.hyperskill.app.analytic.presentation.wrapWithAnalyticLogger
-import org.hyperskill.app.code.domain.repository.CodeRepository
 import org.hyperskill.app.core.domain.BuildVariant
 import org.hyperskill.app.core.presentation.ActionDispatcherOptions
 import org.hyperskill.app.features.data.source.FeaturesDataSource
@@ -11,6 +10,7 @@ import org.hyperskill.app.logging.presentation.wrapWithLogger
 import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor
 import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor
 import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
+import org.hyperskill.app.run_code.domain.repository.RunCodeRepository
 import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
 import org.hyperskill.app.step.domain.model.StepRoute
 import org.hyperskill.app.step_quiz.domain.interactor.StepQuizInteractor
@@ -49,7 +49,7 @@ internal object StepQuizFeatureBuilder {
         sentryInteractor: SentryInteractor,
         onboardingInteractor: OnboardingInteractor,
         featuresDataSource: FeaturesDataSource,
-        codeRepository: CodeRepository,
+        runCodeRepository: RunCodeRepository,
         stepQuizHintsReducer: StepQuizHintsReducer,
         stepQuizHintsActionDispatcher: StepQuizHintsActionDispatcher,
         stepQuizToolbarReducer: StepQuizToolbarReducer,
@@ -79,7 +79,7 @@ internal object StepQuizFeatureBuilder {
             analyticInteractor = analyticInteractor,
             sentryInteractor = sentryInteractor,
             onboardingInteractor = onboardingInteractor,
-            codeRepository = codeRepository,
+            runCodeRepository = runCodeRepository,
             logger = logger.withTag(LOG_TAG)
         )
 
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
index 15ab1f3c0..41d51e757 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt
@@ -7,9 +7,6 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
-import org.hyperskill.app.code.domain.repository.CodeRepository
-import org.hyperskill.app.code.remote.model.RunCodeRequest
 import org.hyperskill.app.core.domain.url.HyperskillUrlPath
 import org.hyperskill.app.core.presentation.ActionDispatcherOptions
 import org.hyperskill.app.features.data.source.FeaturesDataSource
@@ -18,6 +15,9 @@ import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor
 import org.hyperskill.app.profile.domain.model.freemiumChargeLimitsStrategy
 import org.hyperskill.app.profile.domain.model.isFreemiumWrongSubmissionChargeLimitsEnabled
 import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
+import org.hyperskill.app.run_code.domain.repository.RunCodeRepository
+import org.hyperskill.app.run_code.remote.model.RunCodeRequest
 import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
 import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransactionBuilder
 import org.hyperskill.app.sentry.domain.withTransaction
@@ -52,7 +52,7 @@ internal class StepQuizActionDispatcher(
     private val analyticInteractor: AnalyticInteractor,
     private val sentryInteractor: SentryInteractor,
     private val onboardingInteractor: OnboardingInteractor,
-    private val codeRepository: CodeRepository,
+    private val runCodeRepository: RunCodeRepository,
     private val logger: Logger
 ) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {
 
@@ -230,7 +230,7 @@ internal class StepQuizActionDispatcher(
                     )
                 }
 
-                val codeExecutionDeferred = async {
+                val runCodeExecutionDeferred = async {
                     runCode(step = action.step, reply = reply)
                 }
 
@@ -239,7 +239,7 @@ internal class StepQuizActionDispatcher(
                 Message.CreateSubmissionSuccess(
                     submission = createSubmissionResult.newSubmission,
                     newAttempt = createSubmissionResult.newAttempt,
-                    codeExecutionResult = codeExecutionDeferred.await().getOrNull()
+                    runCodeExecutionResult = runCodeExecutionDeferred.await().getOrNull()
                 )
             }
         }.let(::onNewMessage)
@@ -293,9 +293,9 @@ internal class StepQuizActionDispatcher(
     private suspend fun runCode(
         step: Step,
         reply: Reply
-    ): Result<CodeExecutionResult?> =
+    ): Result<RunCodeExecutionResult?> =
         if (step.block.name == BlockName.CODE && reply.code != null) {
-            codeRepository.runCode(
+            runCodeRepository.runCode(
                 RunCodeRequest(
                     stdin = step.block.options.samples?.firstOrNull()?.input ?: "Empty stdin",
                     language = reply.language ?: "",
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 9cb6e4f45..111654d9a 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
@@ -2,9 +2,9 @@ package org.hyperskill.app.step_quiz.presentation
 
 import kotlinx.serialization.Serializable
 import org.hyperskill.app.analytic.domain.model.AnalyticEvent
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.onboarding.domain.model.ProblemsOnboardingFlags
 import org.hyperskill.app.problems_limit_info.domain.model.ProblemsLimitInfoModalContext
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
 import org.hyperskill.app.step.domain.model.Step
 import org.hyperskill.app.step.domain.model.StepContext
 import org.hyperskill.app.step.domain.model.StepRoute
@@ -39,7 +39,7 @@ object StepQuizFeature {
             val isProblemsLimitReached: Boolean,
             internal val isTheoryAvailable: Boolean,
             internal val wrongSubmissionsCount: Int = 0,
-            internal val codeExecutionResult: CodeExecutionResult? = null
+            internal val runCodeExecutionResult: RunCodeExecutionResult? = null
         ) : StepQuizState
 
         data object NetworkError : StepQuizState
@@ -82,7 +82,7 @@ object StepQuizFeature {
         data class CreateSubmissionSuccess(
             val submission: Submission,
             val newAttempt: Attempt? = null,
-            val codeExecutionResult: CodeExecutionResult? = null
+            val runCodeExecutionResult: RunCodeExecutionResult? = null
         ) : Message
         data object CreateSubmissionNetworkError : Message
         data class CreateSubmissionReplyValidationResult(
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 42b7e3925..0bea6b0ef 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
@@ -460,7 +460,7 @@ internal class StepQuizReducer(
                         currentWrongSubmissionCount = state.stepQuizState.wrongSubmissionsCount,
                         currentSubmissionStatus = submissionStatus
                     ),
-                    codeExecutionResult = message.codeExecutionResult
+                    runCodeExecutionResult = message.runCodeExecutionResult
                 )
             ) to buildSet {
                 if (submissionStatus == SubmissionStatus.WRONG &&
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
index 18b8b128e..0f3d85f8d 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizFeedbackMapper.kt
@@ -1,8 +1,8 @@
 package org.hyperskill.app.step_quiz.view.mapper
 
 import org.hyperskill.app.SharedResources
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.core.view.mapper.ResourceProvider
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
 import org.hyperskill.app.step.domain.model.BlockName
 import org.hyperskill.app.step.domain.model.Step
 import org.hyperskill.app.step.domain.model.areCommentsAvailable
@@ -44,11 +44,11 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
         return when (submission?.status) {
             SubmissionStatus.CORRECT ->
                 StepQuizFeedbackState.Correct(
-                    hint = getHint(stepQuizState.step, submission, stepQuizState.codeExecutionResult)
+                    hint = getHint(stepQuizState.step, submission, stepQuizState.runCodeExecutionResult)
                 )
             SubmissionStatus.EVALUATION -> StepQuizFeedbackState.Evaluation(
-                if (isCodeExecutionLaunched(stepQuizState.step)) {
-                    StepQuizFeedbackState.Hint.FromCodeExecution.Loading
+                if (isRunCodeExecutionLaunched(stepQuizState.step)) {
+                    StepQuizFeedbackState.Hint.FromRunCodeExecution.Loading
                 } else {
                     null
                 }
@@ -93,7 +93,7 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
                 null -> null
             },
             actionType = wrongSubmissionAction,
-            hint = getHint(stepQuizState.step, submission, stepQuizState.codeExecutionResult)
+            hint = getHint(stepQuizState.step, submission, stepQuizState.runCodeExecutionResult)
         )
     }
 
@@ -118,13 +118,13 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
     private fun getHint(
         step: Step,
         submission: Submission,
-        codeExecutionResult: CodeExecutionResult?
+        runCodeExecutionResult: RunCodeExecutionResult?
     ): StepQuizFeedbackState.Hint? {
-        val shouldUseCodeExecutionHint =
+        val shouldUseRunCodeExecutionHint =
             submission.status == SubmissionStatus.CORRECT &&
-                isCodeExecutionLaunched(step) && codeExecutionResult != null
-        return if (shouldUseCodeExecutionHint) {
-            getCodeExecutionFeedback(codeExecutionResult)
+                isRunCodeExecutionLaunched(step) && runCodeExecutionResult != null
+        return if (shouldUseRunCodeExecutionHint) {
+            getRunCodeExecutionFeedback(runCodeExecutionResult)
         } else {
             val text = submission
                 .hint
@@ -139,19 +139,19 @@ class StepQuizFeedbackMapper(private val resourcesProvider: ResourceProvider) {
         }
     }
 
-    private fun isCodeExecutionLaunched(step: Step): Boolean =
+    private fun isRunCodeExecutionLaunched(step: Step): Boolean =
         step.block.name == BlockName.CODE
 
-    private fun getCodeExecutionFeedback(
-        codeExecutionResult: CodeExecutionResult?
-    ): StepQuizFeedbackState.Hint.FromCodeExecution.Result? =
+    private fun getRunCodeExecutionFeedback(
+        runCodeExecutionResult: RunCodeExecutionResult?
+    ): StepQuizFeedbackState.Hint.FromRunCodeExecution.Result? =
         when {
-            codeExecutionResult != null -> {
-                val stdout = codeExecutionResult.stdout
+            runCodeExecutionResult != null -> {
+                val stdout = runCodeExecutionResult.stdout
                 if (!stdout.isNullOrEmpty()) {
-                    StepQuizFeedbackState.Hint.FromCodeExecution.Result(
-                        input = codeExecutionResult.stdin?.takeIf { it.isNotEmpty() }?.trim(),
-                        output = codeExecutionResult.stdout.trim()
+                    StepQuizFeedbackState.Hint.FromRunCodeExecution.Result(
+                        input = runCodeExecutionResult.stdin?.takeIf { it.isNotEmpty() }?.trim(),
+                        output = runCodeExecutionResult.stdout.trim()
                     )
                 } else {
                     null
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
index 4709b9299..45e65b339 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/model/StepQuizFeedbackState.kt
@@ -21,7 +21,7 @@ sealed interface StepQuizFeedbackState {
 
     data class RejectedSubmission(val message: String) : StepQuizFeedbackState
 
-    data class Evaluation(val hint: Hint.FromCodeExecution?) : StepQuizFeedbackState
+    data class Evaluation(val hint: Hint.FromRunCodeExecution?) : StepQuizFeedbackState
 
     data class ValidationFailed(val message: String) : StepQuizFeedbackState
 
@@ -33,12 +33,12 @@ sealed interface StepQuizFeedbackState {
             val useLatex: Boolean
         ) : Hint
 
-        sealed interface FromCodeExecution : Hint {
-            data object Loading : FromCodeExecution
+        sealed interface FromRunCodeExecution : Hint {
+            data object Loading : FromRunCodeExecution
             data class Result(
                 val input: String?,
                 val output: String
-            ) : FromCodeExecution
+            ) : FromRunCodeExecution
         }
     }
 }
\ No newline at end of file
diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml
index 3952b69cb..0d693244f 100644
--- a/shared/src/commonMain/moko-resources/base/strings.xml
+++ b/shared/src/commonMain/moko-resources/base/strings.xml
@@ -173,6 +173,7 @@
     <string name="step_quiz_code_editor_title">Code editor</string>
     <string name="step_quiz_code_language_template">(%s)</string>
     <string name="step_quiz_code_execution_title">Running test cases</string>
+    <string name="step_quiz_code_execution_test_case_1">Case 1</string>
     <string name="step_quiz_code_execution_input">Program input:</string>
     <string name="step_quiz_code_execution_output">Program output:</string>
 
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
index 80f5cddfa..8f49609f6 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizFeedbackMapperTest.kt
@@ -4,10 +4,10 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertIs
 import org.hyperskill.ResourceProviderStub
-import org.hyperskill.app.code.domain.model.CodeExecutionResult
 import org.hyperskill.app.comments.domain.model.Comment
 import org.hyperskill.app.comments.domain.model.CommentStatisticsEntry
 import org.hyperskill.app.comments.domain.model.CommentThread
+import org.hyperskill.app.run_code.domain.model.RunCodeExecutionResult
 import org.hyperskill.app.step.domain.model.Block
 import org.hyperskill.app.step.domain.model.BlockName
 import org.hyperskill.app.step.domain.model.Step
@@ -132,7 +132,7 @@ class StepQuizFeedbackMapperTest {
                 status = SubmissionStatus.CORRECT,
                 hint = null
             ),
-            codeExecutionResult = CodeExecutionResult(
+            runCodeExecutionResult = RunCodeExecutionResult(
                 language = "kotlin",
                 stdin = stdin,
                 stdout = stdout,
@@ -142,7 +142,7 @@ class StepQuizFeedbackMapperTest {
 
         val viewState = mapper.map(state)
 
-        val expectedHint = StepQuizFeedbackState.Hint.FromCodeExecution.Result(
+        val expectedHint = StepQuizFeedbackState.Hint.FromRunCodeExecution.Result(
             input = stdin,
             output = stdout
         )
@@ -323,7 +323,7 @@ class StepQuizFeedbackMapperTest {
         wrongSubmissionCount: Int = 0,
         step: Step = Step.stub(id = 0),
         stepQuizHintsState: StepQuizHintsFeature.State = StepQuizHintsFeature.State.Idle,
-        codeExecutionResult: CodeExecutionResult? = null
+        runCodeExecutionResult: RunCodeExecutionResult? = null
     ): StepQuizFeature.State =
         StepQuizFeature.State(
             stepQuizState = StepQuizFeature.StepQuizState.AttemptLoaded(
@@ -336,7 +336,7 @@ class StepQuizFeedbackMapperTest {
                 isProblemsLimitReached = false,
                 isTheoryAvailable = false,
                 wrongSubmissionsCount = wrongSubmissionCount,
-                codeExecutionResult = codeExecutionResult
+                runCodeExecutionResult = runCodeExecutionResult
             ),
             stepQuizHintsState = stepQuizHintsState,
             stepQuizToolbarState = StepQuizToolbarFeature.State.Idle,

From 83e70b7815314caa0982ad2aee5caf7d5a89fe8a Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Fri, 20 Sep 2024 09:50:27 +0000
Subject: [PATCH 18/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 9f56fbeda..e9fb9b9e9 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '555'
\ No newline at end of file
+versionCode = '556'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index e3ceb76ef..6510468a8 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>584</string>
+	<string>585</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index eeaf44679..0a36925f0 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5797,7 +5797,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5818,7 +5818,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5839,7 +5839,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5860,7 +5860,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5881,7 +5881,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5910,7 +5910,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6056,7 +6056,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6092,7 +6092,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 584;
+				CURRENT_PROJECT_VERSION = 585;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 2ff1f337d..0b0d5c6da 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>584</string>
+	<string>585</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index cfa626a8b..f2c791a29 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>584</string>
+	<string>585</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 9f8956b40..308f7315f 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>584</string>
+	<string>585</string>
 </dict>
 </plist>

From 1921247ce3a3a81203790c60490b97b3bf3a7881 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Mon, 23 Sep 2024 13:57:17 +0900
Subject: [PATCH 19/27] Shared: Fake topics in mobile apps adopted course study
 plan (#1187)

^ALTAPPS-1355
---
 config/detekt/baseline.xml                    |  1 +
 .../StudyPlanWidgetFakeTopicsFeature.kt       | 39 ++++++++++++
 .../presentation/StudyPlanWidgetReducer.kt    | 59 +++++++++++++++----
 .../StudyPlanWidgetStateExtensions.kt         | 18 +++---
 .../mapper/StudyPlanWidgetViewStateMapper.kt  |  6 +-
 5 files changed, 104 insertions(+), 19 deletions(-)
 create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt

diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index bb7d6eeb2..e86114ef5 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -71,6 +71,7 @@
     <ID>LongMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSuggestionClicked( state: State, message: Message.SuggestionClicked ): StepQuizCodeBlanksReducerResult?</ID>
     <ID>LongMethod:StepQuizCodeBlanksViewStateMapper.kt$StepQuizCodeBlanksViewStateMapper$private fun mapContentState( state: StepQuizCodeBlanksFeature.State.Content ): StepQuizCodeBlanksViewState.Content</ID>
     <ID>LongMethod:StreakFreezeDialogFragment.kt$StreakFreezeDialogFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
+    <ID>LongMethod:StudyPlanWidgetReducer.kt$StudyPlanWidgetReducer$private fun handleLearningActivitiesWithSectionsFetchSuccess( state: State, message: StudyPlanWidgetFeature.LearningActivitiesWithSectionsFetchResult.Success ): StudyPlanWidgetReducerResult</ID>
     <ID>LongMethod:TrackProgressContent.kt$@Composable fun TrackProgressContent( viewState: ProgressScreenViewState.TrackProgressViewState.Content, onNewMessage: (ProgressScreenFeature.Message) -&gt; Unit, modifier: Modifier = Modifier )</ID>
     <ID>LongParameterList:AppInteractor.kt$AppInteractor$( private val appRepository: AppRepository, private val authInteractor: AuthInteractor, private val currentProfileStateRepository: CurrentProfileStateRepository, private val userStorageInteractor: UserStorageInteractor, private val analyticInteractor: AnalyticInteractor, private val progressesRepository: ProgressesRepository, private val trackRepository: TrackRepository, private val providersRepository: ProvidersRepository, private val projectsRepository: ProjectsRepository, private val shareStreakRepository: ShareStreakRepository, private val pushNotificationsInteractor: PushNotificationsInteractor )</ID>
     <ID>LongParameterList:AuthRemoteDataSourceImpl.kt$AuthRemoteDataSourceImpl$( private val authCacheMutex: Mutex, private val deauthorizationFlow: Flow&lt;UserDeauthorized&gt;, private val authSocialHttpClient: HttpClient, private val authCredentialsHttpClient: HttpClient, private val networkEndpointConfigInfo: NetworkEndpointConfigInfo, private val json: Json, private val settings: Settings )</ID>
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
new file mode 100644
index 000000000..23b146bba
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
@@ -0,0 +1,39 @@
+package org.hyperskill.app.study_plan.widget.presentation
+
+import org.hyperskill.app.core.domain.model.ContentType
+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.subscriptions.domain.model.Subscription
+import org.hyperskill.app.subscriptions.domain.model.isFreemium
+
+internal object StudyPlanWidgetFakeTopicsFeature {
+    private const val PYTHON_MOBILE_ADOPTED_TRACK_ID = 139L
+
+    private val topicsTitles: List<String> =
+        listOf(
+            "Fake topic 1",
+            "Fake topic 2",
+            "Fake topic 3",
+            "Fake topic 4",
+            "Fake topic 5"
+        )
+
+    val topics: List<LearningActivity> by lazy {
+        topicsTitles.mapIndexed { index, title ->
+            LearningActivity(
+                id = -index - 1L,
+                stateValue = LearningActivityState.TODO.value,
+                targetId = null,
+                targetType = ContentType.UNKNOWN,
+                typeValue = LearningActivityType.LEARN_TOPIC.value,
+                title = title,
+                topicId = null
+            )
+        }
+    }
+    val topicsIds: Set<Long> by lazy { topics.map { it.id }.toSet() }
+
+    fun isFakeTopicsFeatureAvailable(trackId: Long?, subscription: Subscription): Boolean =
+        trackId == PYTHON_MOBILE_ADOPTED_TRACK_ID && subscription.isFreemium
+}
\ No newline at end of file
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 d0dfda961..447120786 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
@@ -145,8 +145,42 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
         state: State,
         message: StudyPlanWidgetFeature.LearningActivitiesWithSectionsFetchResult.Success
     ): StudyPlanWidgetReducerResult {
-        val learningActivitiesIds = message.learningActivities.map { it.id }.toSet()
-        val visibleSections = getVisibleSections(message.studyPlanSections, learningActivitiesIds)
+        val isFakeTopicsFeatureAvailable =
+            StudyPlanWidgetFakeTopicsFeature.isFakeTopicsFeatureAvailable(
+                trackId = state.profile?.trackId,
+                subscription = message.subscription
+            )
+        val fakeTopics = if (isFakeTopicsFeatureAvailable) {
+            StudyPlanWidgetFakeTopicsFeature.topics
+        } else {
+            emptyList()
+        }
+
+        val studyPlanSections =
+            if (isFakeTopicsFeatureAvailable) {
+                message.studyPlanSections.map { section ->
+                    if (section.type == StudyPlanSectionType.ROOT_TOPICS) {
+                        section.copy(
+                            isVisible = true,
+                            activities = section.activities + fakeTopics.map { it.id }
+                        )
+                    } else {
+                        section
+                    }
+                }
+            } else {
+                message.studyPlanSections
+            }
+
+        val learningActivities =
+            if (isFakeTopicsFeatureAvailable) {
+                message.learningActivities + fakeTopics
+            } else {
+                message.learningActivities
+            }
+        val learningActivitiesIds = learningActivities.map { it.id }.toSet()
+
+        val visibleSections = getVisibleSections(studyPlanSections, learningActivitiesIds)
         val currentSectionId = visibleSections.firstOrNull()?.id ?: return state.copy(
             studyPlanSections = emptyMap(),
             sectionsStatus = ContentStatus.LOADED,
@@ -156,15 +190,18 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
 
         val supportedSections = visibleSections
             .filter { studyPlanSection ->
-                // ALTAPPS-1186: We should hide next project section for freemium users
-                if (!message.subscription.type.isProjectSelectionEnabled) {
-                    studyPlanSection.type != StudyPlanSectionType.NEXT_PROJECT
-                } else {
-                    true
+                when (studyPlanSection.type) {
+                    StudyPlanSectionType.NEXT_PROJECT ->
+                        // ALTAPPS-1186: We should hide next project section for freemium users
+                        message.subscription.type.isProjectSelectionEnabled
+                    StudyPlanSectionType.NEXT_TRACK ->
+                        // ALTAPPS-1355: We should hide next track section for freemium users
+                        !isFakeTopicsFeatureAvailable
+                    else -> true
                 }
             }
 
-        val studyPlanSections = supportedSections.associate { studyPlanSection ->
+        val resultStudyPlanSections = supportedSections.associate { studyPlanSection ->
             studyPlanSection.id to StudyPlanWidgetFeature.StudyPlanSectionInfo(
                 studyPlanSection = studyPlanSection,
                 isExpanded = studyPlanSection.id == currentSectionId,
@@ -179,11 +216,11 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
         }
 
         val loadedSectionsState = state.copy(
-            studyPlanSections = studyPlanSections,
+            studyPlanSections = resultStudyPlanSections,
             sectionsStatus = ContentStatus.LOADED,
             isRefreshing = false,
             learnedTopicsCount = message.learnedTopicsCount,
-            activities = message.learningActivities.associateBy { it.id },
+            activities = learningActivities.associateBy { it.id },
             subscriptionLimitType = message.subscriptionLimitType
         )
 
@@ -191,7 +228,7 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
             handleNewActivities(
                 loadedSectionsState,
                 sectionId = currentSectionId,
-                activities = message.learningActivities,
+                activities = learningActivities,
                 targetPage = SectionPage.MAIN
             )
         } else {
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetStateExtensions.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetStateExtensions.kt
index 6fa4b2486..ea5fc568b 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetStateExtensions.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetStateExtensions.kt
@@ -109,13 +109,17 @@ internal fun StudyPlanWidgetFeature.State.isActivityLocked(
     sectionId: Long,
     activityId: Long,
 ): Boolean {
-    val unlockedActivitiesCount = getUnlockedActivitiesCount(sectionId)
-    return unlockedActivitiesCount != null &&
-        getLoadedSectionActivities(sectionId)
-            .take(unlockedActivitiesCount)
-            .map { it.id }
-            .contains(activityId)
-            .not()
+    if (StudyPlanWidgetFakeTopicsFeature.topicsIds.contains(activityId)) {
+        return true
+    } else {
+        val unlockedActivitiesCount = getUnlockedActivitiesCount(sectionId)
+        return unlockedActivitiesCount != null &&
+            getLoadedSectionActivities(sectionId)
+                .take(unlockedActivitiesCount)
+                .map { it.id }
+                .contains(activityId)
+                .not()
+    }
 }
 
 /**
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/mapper/StudyPlanWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/mapper/StudyPlanWidgetViewStateMapper.kt
index 88b4c1734..b6b0a8348 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/mapper/StudyPlanWidgetViewStateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/view/mapper/StudyPlanWidgetViewStateMapper.kt
@@ -5,6 +5,7 @@ import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter
 import org.hyperskill.app.learning_activities.domain.model.LearningActivity
 import org.hyperskill.app.learning_activities.domain.model.LearningActivityState
 import org.hyperskill.app.learning_activities.view.mapper.LearningActivityTextsMapper
+import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFakeTopicsFeature
 import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature
 import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.ContentStatus
 import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature.PageContentStatus
@@ -106,12 +107,15 @@ class StudyPlanWidgetViewStateMapper(private val dateFormatter: SharedDateFormat
             emptyActivitiesState
         } else {
             val unlockedActivitiesCount = state.getUnlockedActivitiesCount(sectionId)
+            val fakeTopicsIds = StudyPlanWidgetFakeTopicsFeature.topicsIds
             SectionContent.Content(
                 sectionItems = loadedActivities.mapIndexed { index, activity ->
+                    val isLocked = fakeTopicsIds.contains(activity.id) ||
+                        (unlockedActivitiesCount != null && index + 1 > unlockedActivitiesCount)
                     mapSectionItem(
                         activity = activity,
                         currentActivityId = currentActivityId,
-                        isLocked = unlockedActivitiesCount != null && index + 1 > unlockedActivitiesCount
+                        isLocked = isLocked
                     )
                 },
                 nextPageLoadingState = mapPageContentStatusToViewState(sectionInfo.nextPageContentStatus),

From 9186b137d811bab1e8d52bf29da06358c84b868a Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 23 Sep 2024 04:57:59 +0000
Subject: [PATCH 20/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index e9fb9b9e9..096418add 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '556'
\ No newline at end of file
+versionCode = '557'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index 6510468a8..98a051d93 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>585</string>
+	<string>586</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 0a36925f0..d27f9d5d6 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5797,7 +5797,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5818,7 +5818,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5839,7 +5839,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5860,7 +5860,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5881,7 +5881,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5910,7 +5910,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6056,7 +6056,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6092,7 +6092,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 585;
+				CURRENT_PROJECT_VERSION = 586;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 0b0d5c6da..990ab0611 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>585</string>
+	<string>586</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index f2c791a29..22907cfa3 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>585</string>
+	<string>586</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 308f7315f..1e0ea6ea9 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>585</string>
+	<string>586</string>
 </dict>
 </plist>

From 9d7395e5e6ab6cd3904ff981093aa0d99965c108 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Mon, 23 Sep 2024 14:49:32 +0900
Subject: [PATCH 21/27] Shared: Fix code blanks elif else blocks suggestions
 (#1188)

^ALTAPPS-1346
---
 .../template/CodeBlanksTemplateMapper.kt      | 38 +++++++++++++------
 .../StepQuizCodeBlanksResolver.kt             |  6 ++-
 2 files changed, 31 insertions(+), 13 deletions(-)

diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
index ec0d157dd..cfc1d3bb7 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/template/CodeBlanksTemplateMapper.kt
@@ -30,14 +30,11 @@ internal object CodeBlanksTemplateMapper {
     private fun mapCodeBlanksTemplate(
         codeBlanksTemplate: List<CodeBlockTemplateEntry>,
         step: Step
-    ): List<CodeBlock> {
-        val supportedEntries = codeBlanksTemplate.filter { it.type != CodeBlockTemplateEntryType.UNKNOWN }
-        return if (supportedEntries.isEmpty()) {
-            emptyList()
-        } else {
-            supportedEntries.map { mapCodeBlockTemplateEntry(entry = it, step = step) }
-        }
-    }
+    ): List<CodeBlock> =
+        codeBlanksTemplate
+            .filter { it.type != CodeBlockTemplateEntryType.UNKNOWN }
+            .map { mapCodeBlockTemplateEntry(entry = it, step = step) }
+            .let { setSuggestionsForBlankCodeBlocks(codeBlocks = it, step = step) }
 
     private fun mapCodeBlockTemplateEntry(
         entry: CodeBlockTemplateEntry,
@@ -49,10 +46,7 @@ internal object CodeBlanksTemplateMapper {
                     isActive = entry.isActive,
                     indentLevel = entry.indentLevel,
                     isDeleteForbidden = entry.isDeleteForbidden,
-                    suggestions = StepQuizCodeBlanksResolver.getSuggestionsForBlankCodeBlock(
-                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step),
-                        availableConditions = step.block.options.codeBlanksAvailableConditions ?: emptySet()
-                    )
+                    suggestions = emptyList()
                 )
             CodeBlockTemplateEntryType.PRINT ->
                 CodeBlock.Print(
@@ -140,6 +134,26 @@ internal object CodeBlanksTemplateMapper {
         }
     }
 
+    private fun setSuggestionsForBlankCodeBlocks(
+        codeBlocks: List<CodeBlock>,
+        step: Step
+    ): List<CodeBlock> =
+        codeBlocks.mapIndexed { index, codeBlock ->
+            if (codeBlock is CodeBlock.Blank) {
+                codeBlock.copy(
+                    suggestions = StepQuizCodeBlanksResolver.getSuggestionsForBlankCodeBlock(
+                        index = index,
+                        indentLevel = codeBlock.indentLevel,
+                        codeBlocks = codeBlocks,
+                        isVariableSuggestionAvailable = StepQuizCodeBlanksResolver.isVariableSuggestionsAvailable(step),
+                        availableConditions = step.block.options.codeBlanksAvailableConditions ?: emptySet()
+                    )
+                )
+            } else {
+                codeBlock
+            }
+        }
+
     private fun createMathExpressionsCodeBlocks(step: Step): List<CodeBlock> =
         listOf(
             CodeBlock.Variable(
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
index ecc0a2a8b..f0b629948 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksResolver.kt
@@ -44,7 +44,11 @@ internal object StepQuizCodeBlanksResolver {
                 }
 
             else ->
-                listOf(Suggestion.Print)
+                if (availableConditions.contains(Suggestion.IfStatement.text)) {
+                    listOf(Suggestion.Print, Suggestion.IfStatement)
+                } else {
+                    listOf(Suggestion.Print)
+                }
         }
 
     fun areElifAndElseStatementsSuggestionsAvailable(

From 1b30871c062d5b1380df3c20f1e08fa8cee38365 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 23 Sep 2024 05:50:08 +0000
Subject: [PATCH 22/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 096418add..b2a0630f5 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '557'
\ No newline at end of file
+versionCode = '558'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index 98a051d93..d5c4a4a28 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>586</string>
+	<string>587</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index d27f9d5d6..47be5d72f 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5797,7 +5797,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5818,7 +5818,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5839,7 +5839,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5860,7 +5860,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5881,7 +5881,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5910,7 +5910,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6056,7 +6056,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6092,7 +6092,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 586;
+				CURRENT_PROJECT_VERSION = 587;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index 990ab0611..f8c6f386a 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>586</string>
+	<string>587</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 22907cfa3..80bd86b87 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>586</string>
+	<string>587</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 1e0ea6ea9..6f0a7be3e 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>586</string>
+	<string>587</string>
 </dict>
 </plist>

From 252349f52d9a7b92eefb920bf8a40f561608f910 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Mon, 23 Sep 2024 17:39:52 +0900
Subject: [PATCH 23/27] ALTAPPS-1344: Fix resolve best value badge

---
 .../hyperskill/app/paywall/view/PaywallViewStateMapper.kt   | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt
index 449fbddbf..e92553b60 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/paywall/view/PaywallViewStateMapper.kt
@@ -46,9 +46,8 @@ internal class PaywallViewStateMapper(
     private fun getContentViewState(state: State.Content): ViewStateContent.Content =
         ViewStateContent.Content(
             buyButtonText = resourceProvider.getString(SharedResources.strings.paywall_subscription_start_btn),
-            subscriptionProducts = state.subscriptionProducts.mapIndexed { i, product ->
+            subscriptionProducts = state.subscriptionProducts.map { product ->
                 mapSubscriptionProductToSubscriptionOption(
-                    index = i,
                     product = product,
                     isSelected = product.id == state.selectedProductId
                 )
@@ -56,7 +55,6 @@ internal class PaywallViewStateMapper(
         )
 
     private fun mapSubscriptionProductToSubscriptionOption(
-        index: Int,
         product: SubscriptionProduct,
         isSelected: Boolean
     ): ViewStateContent.SubscriptionProduct =
@@ -75,7 +73,7 @@ internal class PaywallViewStateMapper(
                 SharedResources.strings.paywall_subscription_month_price,
                 product.formattedPricePerMonth
             ),
-            isBestValue = index == 0,
+            isBestValue = product.period == SubscriptionPeriod.YEAR,
             isSelected = isSelected
         )
 }
\ No newline at end of file

From 05fbafe96f5401851820dd5b3995ea4d76fc229f Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 23 Sep 2024 08:43:45 +0000
Subject: [PATCH 24/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index b2a0630f5..75a6f9f73 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '558'
\ No newline at end of file
+versionCode = '559'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index d5c4a4a28..552830647 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>587</string>
+	<string>588</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 47be5d72f..e7cfc81b2 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5797,7 +5797,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5818,7 +5818,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5839,7 +5839,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5860,7 +5860,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5881,7 +5881,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5910,7 +5910,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6056,7 +6056,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6092,7 +6092,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 587;
+				CURRENT_PROJECT_VERSION = 588;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index f8c6f386a..d1ec1d5ca 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>587</string>
+	<string>588</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index 80bd86b87..edd38bd74 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>587</string>
+	<string>588</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index 6f0a7be3e..eedfa6b92 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>587</string>
+	<string>588</string>
 </dict>
 </plist>

From e8b2671ab24331308b5fa3cebcc7933e5588998f Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Mon, 23 Sep 2024 20:23:46 +0900
Subject: [PATCH 25/27] ALTAPPS-1355: Fix isFakeTopicsFeatureAvailable

---
 .../widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
index 23b146bba..3caf6a3b4 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
@@ -35,5 +35,6 @@ internal object StudyPlanWidgetFakeTopicsFeature {
     val topicsIds: Set<Long> by lazy { topics.map { it.id }.toSet() }
 
     fun isFakeTopicsFeatureAvailable(trackId: Long?, subscription: Subscription): Boolean =
-        trackId == PYTHON_MOBILE_ADOPTED_TRACK_ID && subscription.isFreemium
+        trackId == PYTHON_MOBILE_ADOPTED_TRACK_ID &&
+            (subscription.isFreemium || subscription.type == SubscriptionType.MOBILE_CONTENT_TRIAL)
 }
\ No newline at end of file

From 10fb8e4f526200da6934f2c6e80e8c607b5511d6 Mon Sep 17 00:00:00 2001
From: Ivan Magda <ivan.magda@hyperskill.org>
Date: Mon, 23 Sep 2024 20:24:11 +0900
Subject: [PATCH 26/27] ALTAPPS-1355: Use real topics titles

---
 .../StudyPlanWidgetFakeTopicsFeature.kt       | 43 ++++++++++++++++---
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
index 3caf6a3b4..bcf324d0f 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/widget/presentation/StudyPlanWidgetFakeTopicsFeature.kt
@@ -5,6 +5,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.subscriptions.domain.model.Subscription
+import org.hyperskill.app.subscriptions.domain.model.SubscriptionType
 import org.hyperskill.app.subscriptions.domain.model.isFreemium
 
 internal object StudyPlanWidgetFakeTopicsFeature {
@@ -12,11 +13,43 @@ internal object StudyPlanWidgetFakeTopicsFeature {
 
     private val topicsTitles: List<String> =
         listOf(
-            "Fake topic 1",
-            "Fake topic 2",
-            "Fake topic 3",
-            "Fake topic 4",
-            "Fake topic 5"
+            "Advanced conditions",
+            "Comments",
+            "Boolean data type",
+            "Lists",
+            "List comprehension",
+            "Nested lists",
+            "Indexes and slicing",
+            "While loop",
+            "For loop",
+            "Loop control",
+            "Advanced strings",
+            "Declaring functions",
+            "Invoking functions",
+            "Lambda functions",
+            "Tuples",
+            "Sets",
+            "Dictionaries",
+            "Global vs local scopes",
+            "Immutability",
+            "Objects",
+            "Classes",
+            "Class instances",
+            "Class methods",
+            "Methods and attributes",
+            "Magic methods",
+            "Inheritance",
+            "Polymorphism",
+            "Method overriding",
+            "Errors and exceptions",
+            "Exception handling",
+            "User-defined exceptions",
+            "Modules",
+            "Random module",
+            "File modes and permissions",
+            "Files in Python",
+            "Reading files",
+            "Writing files"
         )
 
     val topics: List<LearningActivity> by lazy {

From 9b4ef2e743207a1b3b58d303ab7170da3f827de5 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Mon, 23 Sep 2024 11:25:30 +0000
Subject: [PATCH 27/27] Bump build number

---
 gradle/app.versions.toml                         |  2 +-
 .../NotificationServiceExtension/Info.plist      |  2 +-
 .../iosHyperskillApp.xcodeproj/project.pbxproj   | 16 ++++++++--------
 iosHyperskillApp/iosHyperskillApp/Info.plist     |  2 +-
 .../iosHyperskillAppTests/Info.plist             |  2 +-
 .../iosHyperskillAppUITests/Info.plist           |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/gradle/app.versions.toml b/gradle/app.versions.toml
index 75a6f9f73..0440d68a6 100644
--- a/gradle/app.versions.toml
+++ b/gradle/app.versions.toml
@@ -3,4 +3,4 @@ minSdk = '24'
 targetSdk = '34'
 compileSdk = '34'
 versionName = '1.72'
-versionCode = '559'
\ No newline at end of file
+versionCode = '560'
\ No newline at end of file
diff --git a/iosHyperskillApp/NotificationServiceExtension/Info.plist b/iosHyperskillApp/NotificationServiceExtension/Info.plist
index 552830647..80ad64bf5 100644
--- a/iosHyperskillApp/NotificationServiceExtension/Info.plist
+++ b/iosHyperskillApp/NotificationServiceExtension/Info.plist
@@ -9,7 +9,7 @@
 	<key>CFBundleIdentifier</key>
 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
 	<key>CFBundleVersion</key>
-	<string>588</string>
+	<string>589</string>
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundlePackageType</key>
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index e7cfc81b2..aee7473de 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -5797,7 +5797,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
@@ -5818,7 +5818,7 @@
 			buildSettings = {
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = iosHyperskillAppUITests/Info.plist;
@@ -5839,7 +5839,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
@@ -5860,7 +5860,7 @@
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = iosHyperskillAppTests/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -5881,7 +5881,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
@@ -5910,7 +5910,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -6056,7 +6056,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
@@ -6092,7 +6092,7 @@
 				CODE_SIGN_ENTITLEMENTS = iosHyperskillApp/iosHyperskillApp.entitlements;
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 588;
+				CURRENT_PROJECT_VERSION = 589;
 				DEVELOPMENT_ASSET_PATHS = "\"iosHyperskillApp/Preview Content\"";
 				DEVELOPMENT_TEAM = UJ4KC2QN7B;
 				ENABLE_PREVIEWS = YES;
diff --git a/iosHyperskillApp/iosHyperskillApp/Info.plist b/iosHyperskillApp/iosHyperskillApp/Info.plist
index d1ec1d5ca..95f1fa9a6 100644
--- a/iosHyperskillApp/iosHyperskillApp/Info.plist
+++ b/iosHyperskillApp/iosHyperskillApp/Info.plist
@@ -34,7 +34,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>588</string>
+	<string>589</string>
 	<key>FirebaseAppDelegateProxyEnabled</key>
 	<false/>
 	<key>FirebaseMessagingAutoInitEnabled</key>
diff --git a/iosHyperskillApp/iosHyperskillAppTests/Info.plist b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
index edd38bd74..c4031e647 100644
--- a/iosHyperskillApp/iosHyperskillAppTests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppTests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>588</string>
+	<string>589</string>
 </dict>
 </plist>
diff --git a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
index eedfa6b92..7b7516105 100644
--- a/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
+++ b/iosHyperskillApp/iosHyperskillAppUITests/Info.plist
@@ -15,6 +15,6 @@
 	<key>CFBundleShortVersionString</key>
 	<string>1.72</string>
 	<key>CFBundleVersion</key>
-	<string>588</string>
+	<string>589</string>
 </dict>
 </plist>