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) -> 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<UserDeauthorized>, 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>