From a1ce9b3fc0fef0dc480910142749120513f51440 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 25 Sep 2023 14:39:57 +0400 Subject: [PATCH 01/16] Update StepQuizStatsTextMapper.getFormattedStepQuizStats --- .../StepQuiz/ViewData/StepQuizViewDataMapper.swift | 2 +- .../step_quiz/view/mapper/StepQuizStatsTextMapper.kt | 10 +++++----- shared/src/commonMain/resources/MR/base/plurals.xml | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift index ca8c16f936..4c961d9319 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift @@ -31,7 +31,7 @@ final class StepQuizViewDataMapper { } let formattedStats = stepQuizStatsTextMapper.getFormattedStepQuizStats( - solvedByUsersCount: step.solvedBy, + solvedByCount: step.solvedBy, millisSinceLastCompleted: step.millisSinceLastCompleted ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizStatsTextMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizStatsTextMapper.kt index e463635fad..338c9e4207 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizStatsTextMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizStatsTextMapper.kt @@ -8,14 +8,14 @@ class StepQuizStatsTextMapper( private val resourceProvider: ResourceProvider, private val dateFormatter: SharedDateFormatter ) { - fun getFormattedStepQuizStats(solvedByUsersCount: Int, millisSinceLastCompleted: Long?): String? = - if (solvedByUsersCount > 0 && millisSinceLastCompleted != null) { + fun getFormattedStepQuizStats(solvedByCount: Int, millisSinceLastCompleted: Long?): String? = + if (solvedByCount > 0 && millisSinceLastCompleted != null) { resourceProvider.getString( SharedResources.strings.step_quiz_stats_text, resourceProvider.getQuantityString( - SharedResources.plurals.users, - solvedByUsersCount, - solvedByUsersCount + SharedResources.plurals.learners, + solvedByCount, + solvedByCount ), dateFormatter.formatTimeDistance(millisSinceLastCompleted) ) diff --git a/shared/src/commonMain/resources/MR/base/plurals.xml b/shared/src/commonMain/resources/MR/base/plurals.xml index d7f420a6f5..0eee7a55c7 100644 --- a/shared/src/commonMain/resources/MR/base/plurals.xml +++ b/shared/src/commonMain/resources/MR/base/plurals.xml @@ -44,6 +44,10 @@ %d user %d users + + %d learner + %d learners + %d project %d projects From 999df63d5ead6b72960883a9e45c73c9bf768d2c Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 25 Sep 2023 15:54:15 +0400 Subject: [PATCH 02/16] Delete quiz name for code related problems --- .../dialog/CodeStepQuizFullScreenDialogFragment.kt | 2 +- .../Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift | 5 ----- .../StepQuizCode/Views/StepQuizCodeView.swift | 2 -- .../app/step_quiz/view/mapper/StepQuizTitleMapper.kt | 4 ---- shared/src/commonMain/resources/MR/base/strings.xml | 1 - 5 files changed, 1 insertion(+), 13 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fullscreen_code/dialog/CodeStepQuizFullScreenDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fullscreen_code/dialog/CodeStepQuizFullScreenDialogFragment.kt index 0daa969616..192cd5fcf9 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fullscreen_code/dialog/CodeStepQuizFullScreenDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz_fullscreen_code/dialog/CodeStepQuizFullScreenDialogFragment.kt @@ -378,7 +378,7 @@ class CodeStepQuizFullScreenDialogFragment : DialogFragment() { get() = if (lang == ProgrammingLanguage.SQL.serverPrintableName) { org.hyperskill.app.R.string.step_quiz_sql_title } else { - org.hyperskill.app.R.string.step_quiz_code_write_program_text + org.hyperskill.app.R.string.step_quiz_code_title } } } \ No newline at end of file diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift index 4c961d9319..1685f1bdcf 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/ViewData/StepQuizViewDataMapper.swift @@ -51,11 +51,6 @@ final class StepQuizViewDataMapper { return nil } - // Custom title rendering by code quiz - if step.block.name == BlockName.shared.CODE { - return nil - } - return stepQuizTitleMapper.getStepQuizTitle( blockName: step.block.name, isMultipleChoice: KotlinBoolean(bool: dataset.isMultipleChoice), diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index 4dd9c8e2be..3f6155e257 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -26,8 +26,6 @@ struct StepQuizCodeView: View { ) .padding(.horizontal, -LayoutInsets.defaultInset) - StepQuizNameView(text: Strings.StepQuizCode.title) - CodeEditor( code: .constant(viewData.code), codeTemplate: viewData.codeTemplate, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizTitleMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizTitleMapper.kt index 433d10bce7..bb9e4b6b4d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizTitleMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/view/mapper/StepQuizTitleMapper.kt @@ -16,10 +16,6 @@ class StepQuizTitleMapper( resourceProvider.getString(SharedResources.strings.step_quiz_choice_single_choice_title) } } - BlockName.CODE, BlockName.PYCHARM -> - resourceProvider.getString(SharedResources.strings.step_quiz_code_title) - BlockName.SQL -> - resourceProvider.getString(SharedResources.strings.step_quiz_sql_title) BlockName.MATCHING -> resourceProvider.getString(SharedResources.strings.step_quiz_matching_title) BlockName.MATH -> diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 32e66ebb69..a829b7c6c6 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -145,7 +145,6 @@ Memory limit: %d MB Input / Output Info - Write a program Run solution Are you sure? Your code will be lost From 204a2874797d91944abfcd1cfe06a9bc959c4096 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 25 Sep 2023 16:11:24 +0400 Subject: [PATCH 03/16] Delete execution time & memory limits --- .../Shared/Model/BlockOptionsExtensions.swift | 18 ------------- .../Sources/Models/Constants/Strings.swift | 3 --- .../StepQuizCode/StepQuizCodeAssembly.swift | 2 -- .../ViewData/StepQuizCodeViewData.swift | 3 --- .../ViewData/StepQuizCodeViewDataMapper.swift | 27 ------------------- .../Samples/StepQuizCodeSamplesView.swift | 26 ++---------------- .../Details/StepQuizCodeDetailsView.swift | 13 ++------- .../StepQuizCode/Views/StepQuizCodeView.swift | 2 -- .../StepQuizCodeFullScreenAssembly.swift | 2 -- .../StepQuizCodeFullScreenDetailsView.swift | 14 ++-------- .../Views/StepQuizCodeFullScreenView.swift | 4 +-- .../StepQuizPyCharmViewDataMapper.swift | 2 -- .../StepQuizSQLViewDataMapper.swift | 2 -- .../hyperskill/app/step/domain/model/Block.kt | 4 --- .../commonMain/resources/MR/base/strings.xml | 3 --- 15 files changed, 7 insertions(+), 118 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift index cfb700ce54..5c590c6b89 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/Shared/Model/BlockOptionsExtensions.swift @@ -6,8 +6,6 @@ extension Block.Options { isMultipleChoice: Bool? = nil, language: String? = nil, isCheckbox: Bool? = nil, - executionTimeLimit: Int? = nil, - executionMemoryLimit: Int? = nil, limits: [String: Limit]? = nil, codeTemplates: [String: String]? = nil, samples: [[String]]? = nil, @@ -27,26 +25,10 @@ extension Block.Options { return nil }() - let executionTimeLimit: KotlinInt? = { - if let executionTimeLimit = executionTimeLimit { - return KotlinInt(value: Int32(executionTimeLimit)) - } - return nil - }() - - let executionMemoryLimit: KotlinInt? = { - if let executionMemoryLimit = executionMemoryLimit { - return KotlinInt(value: Int32(executionMemoryLimit)) - } - return nil - }() - self.init( isMultipleChoice: isMultipleChoice, language: language, isCheckbox: isCheckbox, - executionTimeLimit: executionTimeLimit, - executionMemoryLimit: executionMemoryLimit, limits: limits, codeTemplates: codeTemplates, samples: samples, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 01f51f872d..917eeafcfb 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -143,9 +143,6 @@ enum Strings { static let detailsTitle = sharedStrings.step_quiz_code_details.localized() static let sampleInputTitleResource = sharedStrings.step_quiz_code_detail_sample_input_title static let sampleOutputTitleResource = sharedStrings.step_quiz_code_detail_sample_output_title - static let timeLimitTitle = sharedStrings.step_quiz_code_detail_execution_time_limit_title.localized() - static let memoryLimitTitle = sharedStrings.step_quiz_code_detail_execution_memory_limit_title.localized() - static let memoryLimitValueResource = sharedStrings.step_quiz_code_detail_execution_memory_limit_value static let runSolutionButton = sharedStrings.step_quiz_code_run_solution_button_text.localized() static let fullScreenDetailsTab = sharedStrings.step_quiz_code_full_screen_details_tab.localized() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeAssembly.swift index e8ebaad796..3f77697e38 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeAssembly.swift @@ -50,8 +50,6 @@ final class StepQuizCodeAssembly: StepQuizChildQuizAssembly { extension StepQuizCodeAssembly { static func makePlaceholder() -> StepQuizCodeAssembly { let blockOptions = Block.Options( - executionTimeLimit: 5, - executionMemoryLimit: 256, limits: [ "kotlin": .init(time: 8, memory: 256) ], diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewData.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewData.swift index 4ab3459834..d794b4e4fa 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewData.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewData.swift @@ -9,9 +9,6 @@ struct StepQuizCodeViewData { let samples: [Sample] - let executionTimeLimit: String? - let executionMemoryLimit: String? - let stepText: String struct Sample: Hashable { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift index e645464a18..f2a68be036 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/ViewData/StepQuizCodeViewDataMapper.swift @@ -40,39 +40,12 @@ class StepQuizCodeViewDataMapper { let samples = mapSamples(blockOptions.samples) - let languageLimits: Limit? = { - guard let languageStringValue = languageStringValue else { - return nil - } - - return blockOptions.limits?[languageStringValue] - }() - let executionTimeLimit: String? = { - guard let timeLimit = languageLimits?.time ?? blockOptions.executionTimeLimit?.int32Value else { - return nil - } - - return formatter.secondsCount(timeLimit) - }() - let executionMemoryLimit: String? = { - guard let memoryLimit = languageLimits?.memory ?? blockOptions.executionMemoryLimit?.int32Value else { - return nil - } - - return resourceProvider.getString( - stringResource: Strings.StepQuizCode.memoryLimitValueResource, - args: KotlinArray(size: 1, init: { _ in NSNumber(value: memoryLimit) }) - ) - }() - return StepQuizCodeViewData( language: language, languageStringValue: languageStringValue, code: reply?.code ?? codeTemplate, codeTemplate: codeTemplate, samples: samples, - executionTimeLimit: executionTimeLimit, - executionMemoryLimit: executionMemoryLimit, stepText: step.block.text ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift index dcbebc6248..1243f6246d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift @@ -3,15 +3,8 @@ import SwiftUI struct StepQuizCodeSamplesView: View { let samples: [StepQuizCodeViewData.Sample] - let executionTimeLimit: String? - let executionMemoryLimit: String? - - private var isEmpty: Bool { - samples.isEmpty && executionTimeLimit == nil && executionMemoryLimit == nil - } - var body: some View { - if isEmpty { + if samples.isEmpty { EmptyView() } else { VStack(spacing: 0) { @@ -19,19 +12,6 @@ struct StepQuizCodeSamplesView: View { StepQuizCodeSampleItemView(title: sample.inputTitle, subtitle: sample.inputValue) StepQuizCodeSampleItemView(title: sample.outputTitle, subtitle: sample.outputValue) } - - if let executionTimeLimit = executionTimeLimit { - StepQuizCodeSampleItemView( - title: Strings.StepQuizCode.timeLimitTitle, - subtitle: executionTimeLimit - ) - } - if let executionMemoryLimit = executionMemoryLimit { - StepQuizCodeSampleItemView( - title: Strings.StepQuizCode.memoryLimitTitle, - subtitle: executionMemoryLimit - ) - } } } } @@ -47,9 +27,7 @@ struct StepQuizCodeSamplesView_Previews: PreviewProvider { outputTitle: "Sample Output 1", outputValue: "true" ) - ], - executionTimeLimit: "Time limit: 8 seconds", - executionMemoryLimit: "Memory limit: 256 MB" + ] ) .previewLayout(.sizeThatFits) .padding() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift index 903a1de97a..b37c56fd6e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift @@ -11,9 +11,6 @@ struct StepQuizCodeDetailsView: View { let samples: [StepQuizCodeViewData.Sample] - let executionTimeLimit: String? - let executionMemoryLimit: String? - private(set) var isAlwaysExpanded = false var onExpandTapped: (() -> Void)? @State private var isExpanded = false @@ -40,9 +37,7 @@ struct StepQuizCodeDetailsView: View { if isExpanded || isAlwaysExpanded { StepQuizCodeSamplesView( - samples: samples, - executionTimeLimit: executionTimeLimit, - executionMemoryLimit: executionMemoryLimit + samples: samples ) } } @@ -85,9 +80,7 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputTitle: "Sample Output 1", outputValue: "true" ) - ], - executionTimeLimit: "Time limit: 8 seconds", - executionMemoryLimit: "Memory limit: 256 MB" + ] ) StepQuizCodeDetailsView( @@ -99,8 +92,6 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputValue: "true" ) ], - executionTimeLimit: "Time limit: 8 seconds", - executionMemoryLimit: "Memory limit: 256 MB", isAlwaysExpanded: true ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index 3f6155e257..020ee6fd54 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -20,8 +20,6 @@ struct StepQuizCodeView: View { StepQuizCodeDetailsView( samples: viewData.samples, - executionTimeLimit: viewData.executionTimeLimit, - executionMemoryLimit: viewData.executionMemoryLimit, onExpandTapped: viewModel.logClickedCodeDetailsEvent ) .padding(.horizontal, -LayoutInsets.defaultInset) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift index ef3002a389..d03f13675f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift @@ -60,8 +60,6 @@ extension StepQuizCodeFullScreenAssembly { outputValue: "true" ) ], - executionTimeLimit: "Time limit: 8 seconds", - executionMemoryLimit: "Memory limit: 256 MB", stepText: """ Enter only the name of the found functional interface with/without the package. Don't write any generic parameters. """ diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift index 1ff421568d..a5df868f94 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift @@ -15,23 +15,15 @@ struct StepQuizCodeFullScreenDetailsView: View { let stepText: String let samples: [StepQuizCodeViewData.Sample] - let executionTimeLimit: String? - let executionMemoryLimit: String? - - private var isDetailsEmpty: Bool { - samples.isEmpty && executionTimeLimit == nil && executionMemoryLimit == nil - } var body: some View { ScrollView { VStack(alignment: .leading, spacing: appearance.spacing) { StepTextView(text: stepText) - if !isDetailsEmpty { + if !samples.isEmpty { StepQuizCodeDetailsView( samples: samples, - executionTimeLimit: executionTimeLimit, - executionMemoryLimit: executionMemoryLimit, isAlwaysExpanded: true ) .padding(.horizontal, -appearance.spacing) @@ -55,9 +47,7 @@ Enter only the name of the found functional interface with/without the package. outputTitle: "Sample Output 1", outputValue: "true" ) - ], - executionTimeLimit: "Time limit: 8 seconds", - executionMemoryLimit: "Memory limit: 256 MB" + ] ) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift index 52651549be..fbbc324475 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift @@ -29,9 +29,7 @@ struct StepQuizCodeFullScreenView: View { TabNavigationLazyView( StepQuizCodeFullScreenDetailsView( stepText: viewData.stepText, - samples: viewData.samples, - executionTimeLimit: viewData.executionTimeLimit, - executionMemoryLimit: viewData.executionMemoryLimit + samples: viewData.samples ) ) .tag(StepQuizCodeFullScreenTab.details) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmViewDataMapper.swift index 43e93780cc..d341694d98 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmViewDataMapper.swift @@ -27,8 +27,6 @@ final class StepQuizPyCharmViewDataMapper: StepQuizCodeViewDataMapper { code: code, codeTemplate: codeTemplate, samples: [], - executionTimeLimit: nil, - executionMemoryLimit: nil, stepText: step.block.text ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/StepQuizSQLViewDataMapper.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/StepQuizSQLViewDataMapper.swift index ee6736831f..2149363cac 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/StepQuizSQLViewDataMapper.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/StepQuizSQLViewDataMapper.swift @@ -9,8 +9,6 @@ final class StepQuizSQLViewDataMapper: StepQuizCodeViewDataMapper { code: reply?.solveSql, codeTemplate: nil, samples: [], - executionTimeLimit: nil, - executionMemoryLimit: nil, stepText: step.block.text ) } 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 5b95a54f9a..09708f1bd5 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 @@ -20,10 +20,6 @@ data class Block( val language: String? = null, @SerialName("is_checkbox") val isCheckbox: Boolean? = null, - @SerialName("execution_time_limit") - val executionTimeLimit: Int? = null, - @SerialName("execution_memory_limit") - val executionMemoryLimit: Int? = null, @SerialName("limits") val limits: Map? = null, @SerialName("code_templates") diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index a829b7c6c6..92cea1f084 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -141,9 +141,6 @@ Write a program Sample Input %d: Sample Output %d: - Time limit: - Memory limit: - %d MB Input / Output Info Run solution Are you sure? From f629081d6eca74198601dac7838edce8ddd1819d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 26 Sep 2023 15:53:35 +0400 Subject: [PATCH 04/16] Add CollapsableStepTextView --- .../project.pbxproj | 14 +++- .../Sources/Models/Constants/Strings.swift | 5 ++ .../StepText/CollapsableStepTextView.swift | 64 +++++++++++++++++++ .../Views/{ => StepText}/StepTextView.swift | 0 .../Modules/StepQuiz/Views/StepQuizView.swift | 9 ++- .../commonMain/resources/MR/base/strings.xml | 1 + 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/{ => StepText}/StepTextView.swift (100%) diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index df41d7eb5a..d68acafc40 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -272,6 +272,7 @@ 2C96743F288831BB0091B6C9 /* StepQuizCodeSamplesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96743E288831BB0091B6C9 /* StepQuizCodeSamplesView.swift */; }; 2C96744228883A180091B6C9 /* StepQuizCodeViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96744128883A180091B6C9 /* StepQuizCodeViewDataMapper.swift */; }; 2C96744428883E710091B6C9 /* BlockOptionsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */; }; + 2C971B852AC2F5DC00868FCE /* CollapsableStepTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */; }; 2C975D662A1128670068FD4E /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */; }; 2C97B0CD298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */; }; 2C97E55A2859A2C500EA1A21 /* StepQuizNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */; }; @@ -870,6 +871,7 @@ 2C96743E288831BB0091B6C9 /* StepQuizCodeSamplesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeSamplesView.swift; sourceTree = ""; }; 2C96744128883A180091B6C9 /* StepQuizCodeViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeViewDataMapper.swift; sourceTree = ""; }; 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockOptionsExtensions.swift; sourceTree = ""; }; + 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableStepTextView.swift; sourceTree = ""; }; 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsFeatureViewStateKsExtensions.swift; sourceTree = ""; }; 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizNameView.swift; sourceTree = ""; }; @@ -2385,6 +2387,15 @@ path = DailyStudyReminders; sourceTree = ""; }; + 2C971B832AC2F5A600868FCE /* StepText */ = { + isa = PBXGroup; + children = ( + 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */, + 2C9F59A629267A530008ADC5 /* StepTextView.swift */, + ); + path = StepText; + sourceTree = ""; + }; 2C99B0FE2A14253B0018627B /* Model */ = { isa = PBXGroup; children = ( @@ -2582,11 +2593,11 @@ isa = PBXGroup; children = ( 2CAE8CF628052F9600E6C83D /* StepHeaderView.swift */, - 2C9F59A629267A530008ADC5 /* StepTextView.swift */, 2C8409522805BF3C009C6BE9 /* StepTheoryContentView.swift */, 2CAE8CF1280525C900E6C83D /* StepView.swift */, 2CAE8D0A280578A200E6C83D /* BottomControls */, 2CD0BA05298B985A0037479A /* Modals */, + 2C971B832AC2F5A600868FCE /* StepText */, ); path = Views; sourceTree = ""; @@ -3997,6 +4008,7 @@ 2CCCA3A12862E62F00D98089 /* StepQuizStringViewData.swift in Sources */, 2C1061A2285C349400EBD614 /* StepQuizChildQuizAssembly.swift in Sources */, 2C11D5CA2A11311900C59238 /* FeedbackGeneratorPreviewView.swift in Sources */, + 2C971B852AC2F5DC00868FCE /* CollapsableStepTextView.swift in Sources */, 2CB279AF28C72AA400EDDCC8 /* DeepLinkRouterProtocol.swift in Sources */, 2C023C86285D927A00D2D5A9 /* StepQuizTableAssembly.swift in Sources */, 2C20FBC4284F67F3006D879E /* ProcessedContentWebView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index 917eeafcfb..ff5bb5159d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -88,14 +88,19 @@ enum Strings { static let quizStatusWrong = sharedStrings.step_quiz_status_wrong_text.localized() 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 continueButton = sharedStrings.step_quiz_continue_button_text.localized() static let retryButton = sharedStrings.step_quiz_retry_button_text.localized() static let sendButton = sharedStrings.step_quiz_send_button_text.localized() static let checkingButton = sharedStrings.step_quiz_checking_button_text.localized() static let discussionsButton = sharedStrings.step_quiz_discussions_button_text.localized() + static let unsupportedText = sharedStrings.step_quiz_unsupported_quiz_text.localized() + static let stepTextHeaderTitle = sharedStrings.step_quiz_step_text_header_title.localized() + enum ResetCodeAlert { static let title = sharedStrings.reset_code_dialog_title.localized() static let text = sharedStrings.reset_code_dialog_explanation.localized() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift new file mode 100644 index 0000000000..93812915ec --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +extension CollapsableStepTextView { + struct Appearance { + let spacing = LayoutInsets.smallInset + + var stepTextViewAppearance = StepTextUIKitView.Appearance() + } +} + +struct CollapsableStepTextView: View { + private(set) var appearance = Appearance() + + var text: String + + @State var isCollapsed = false + + var body: some View { + VStack(alignment: .center, spacing: appearance.spacing) { + Button( + action: { + withAnimation { + isCollapsed.toggle() + } + }, + label: { + HStack(alignment: .center) { + Text(Strings.StepQuiz.stepTextHeaderTitle) + .foregroundColor(.primaryText) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer() + + Image(systemName: "chevron.right") + .imageScale(.small) + .aspectRatio(contentMode: .fit) + .rotationEffect(.radians(isCollapsed ? .zero : (.pi / 2))) + } + .font(.headline) + } + ) + + if !isCollapsed { + StepTextView( + text: text, + appearance: appearance.stepTextViewAppearance, + onContentLoaded: nil + ) + } + } + } +} + +struct CollapsableStepTextView_Previews: PreviewProvider { + static var previews: some View { + CollapsableStepTextView( + text: """ +

Despite the fact that the syntax for different databases may differ, most of them have common standards.

+""" + ) + .padding() + .frame(height: 200) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/StepTextView.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepTextView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/StepTextView.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index f6b54ff3a4..58fd129f07 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -53,7 +53,14 @@ struct StepQuizView: View { StepTextView(text: viewData.stepText) } else { - StepTextView(text: viewData.stepText) + if viewData.quizType.isCodeRelated { + CollapsableStepTextView( + text: viewData.stepText, + isCollapsed: false + ) + } else { + StepTextView(text: viewData.stepText) + } if viewData.stepHasHints { StepQuizHintsAssembly( diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 92cea1f084..1931d50066 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -89,6 +89,7 @@ Solve another one tomorrow to get more You completed %s Continue with next topic + Description See hint From 56bf836898cbffda7253b708965d667179086e9a Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 26 Sep 2023 16:39:21 +0400 Subject: [PATCH 05/16] Update code details view --- .../StepText/CollapsableStepTextView.swift | 3 +- .../Samples/StepQuizCodeSampleItemView.swift | 6 +- .../Samples/StepQuizCodeSamplesView.swift | 32 +++++-- .../Details/StepQuizCodeDetailsView.swift | 91 ++++++++----------- .../StepQuizCode/Views/StepQuizCodeView.swift | 1 - .../StepQuizCodeFullScreenDetailsView.swift | 18 ++-- .../commonMain/resources/MR/base/strings.xml | 2 +- 7 files changed, 76 insertions(+), 77 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift index 93812915ec..f4f9132e09 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift @@ -2,7 +2,7 @@ import SwiftUI extension CollapsableStepTextView { struct Appearance { - let spacing = LayoutInsets.smallInset + let spacing = LayoutInsets.defaultInset var stepTextViewAppearance = StepTextUIKitView.Appearance() } @@ -19,6 +19,7 @@ struct CollapsableStepTextView: View { VStack(alignment: .center, spacing: appearance.spacing) { Button( action: { + #warning("log clicked event") withAnimation { isCollapsed.toggle() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSampleItemView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSampleItemView.swift index 0097d87a1f..f419db2354 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSampleItemView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSampleItemView.swift @@ -17,16 +17,14 @@ struct StepQuizCodeSampleItemView: View { VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: LayoutInsets.defaultInset) { Text(title) - .font(appearance.textFont) .foregroundColor(.secondaryText) Text(subtitle) - .font(appearance.textFont) .foregroundColor(.primaryText) } + .font(appearance.textFont) .padding() - - Divider() + .frame(maxWidth: .infinity, alignment: .leading) } .background(BackgroundView()) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift index 1243f6246d..bfc321f75b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift @@ -4,16 +4,28 @@ struct StepQuizCodeSamplesView: View { let samples: [StepQuizCodeViewData.Sample] var body: some View { - if samples.isEmpty { - EmptyView() - } else { - VStack(spacing: 0) { - ForEach(samples, id: \.self) { sample in - StepQuizCodeSampleItemView(title: sample.inputTitle, subtitle: sample.inputValue) - StepQuizCodeSampleItemView(title: sample.outputTitle, subtitle: sample.outputValue) + VStack(spacing: 0) { + ForEach(Array(zip(samples.indices, samples)), id: \.0) { index, sample in + StepQuizCodeSampleItemView( + title: sample.inputTitle, + subtitle: sample.inputValue + ) + + Divider() + .padding(.horizontal) + + StepQuizCodeSampleItemView( + title: sample.outputTitle, + subtitle: sample.outputValue + ) + + if index != samples.endIndex - 1 { + Divider() + .padding(.horizontal) } } } + .addBorder() } } @@ -26,6 +38,12 @@ struct StepQuizCodeSamplesView_Previews: PreviewProvider { inputValue: "3\n3\n3", outputTitle: "Sample Output 1", outputValue: "true" + ), + .init( + inputTitle: "Sample Input 2", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 2", + outputValue: "true" ) ] ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift index b37c56fd6e..ee33146733 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift @@ -1,71 +1,53 @@ import SwiftUI -extension StepQuizCodeDetailsView { - struct Appearance { - let primaryActionButtonImageWidthHeight: CGFloat = 20 - } -} - struct StepQuizCodeDetailsView: View { - private(set) var appearance = Appearance() - let samples: [StepQuizCodeViewData.Sample] - private(set) var isAlwaysExpanded = false + @State var isExpanded = false + var onExpandTapped: (() -> Void)? - @State private var isExpanded = false var body: some View { - VStack(alignment: .leading, spacing: 0) { - Divider() - if isAlwaysExpanded { - headerContent - } else { - Button( - action: { - onExpandTapped?() - withAnimation { - isExpanded.toggle() - } - }, - label: { - headerContent - } - ) - } - Divider() - - if isExpanded || isAlwaysExpanded { - StepQuizCodeSamplesView( - samples: samples - ) - } + if samples.isEmpty { + EmptyView() + } else { + contentView } } - private var headerContent: some View { - HStack(spacing: LayoutInsets.defaultInset) { - Image(systemName: "info.circle") - .resizable() - .renderingMode(.template) - .aspectRatio(contentMode: .fit) - .frame(widthHeight: appearance.primaryActionButtonImageWidthHeight) + private var contentView: some View { + VStack(alignment: .center, spacing: LayoutInsets.defaultInset) { + Button( + action: { + onExpandTapped?() + + withAnimation { + isExpanded.toggle() + } + }, + label: { + HStack(alignment: .center) { + Text(Strings.StepQuizCode.detailsTitle) + .foregroundColor(.primaryText) + .frame(maxWidth: .infinity, alignment: .leading) - Text(Strings.StepQuizCode.detailsTitle) - .font(.body) + Spacer() - Spacer() + Image(systemName: "chevron.right") + .imageScale(.small) + .aspectRatio(contentMode: .fit) + .rotationEffect(.radians(isExpanded ? (.pi / 2) : .zero)) + } + .font(.headline) + } + ) - if !isAlwaysExpanded { - Image(systemName: "chevron.down") - .imageScale(.small) - .aspectRatio(contentMode: .fit) - .rotationEffect(.radians(isExpanded ? .pi : .zero)) + if isExpanded { + StepQuizCodeSamplesView( + samples: samples + ) } } - .foregroundColor(.secondaryText) - .padding() - .background(BackgroundView()) } } @@ -80,7 +62,8 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputTitle: "Sample Output 1", outputValue: "true" ) - ] + ], + isExpanded: false ) StepQuizCodeDetailsView( @@ -92,7 +75,7 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputValue: "true" ) ], - isAlwaysExpanded: true + isExpanded: true ) } .previewLayout(.sizeThatFits) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index 020ee6fd54..ee7ade3271 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -22,7 +22,6 @@ struct StepQuizCodeView: View { samples: viewData.samples, onExpandTapped: viewModel.logClickedCodeDetailsEvent ) - .padding(.horizontal, -LayoutInsets.defaultInset) CodeEditor( code: .constant(viewData.code), diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift index a5df868f94..db616346a0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift @@ -19,15 +19,15 @@ struct StepQuizCodeFullScreenDetailsView: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: appearance.spacing) { - StepTextView(text: stepText) - - if !samples.isEmpty { - StepQuizCodeDetailsView( - samples: samples, - isAlwaysExpanded: true - ) - .padding(.horizontal, -appearance.spacing) - } + CollapsableStepTextView( + text: stepText, + isCollapsed: false + ) + + StepQuizCodeDetailsView( + samples: samples, + isExpanded: true + ) } .padding() } diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 1931d50066..94f2645842 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -142,7 +142,7 @@ Write a program Sample Input %d: Sample Output %d: - Input / Output Info + Example Run solution Are you sure? Your code will be lost From 488a2d9e79ee8217eee927cf86a79ed16aa5bdce Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 11:48:42 +0400 Subject: [PATCH 06/16] Delete problems limit UI --- .../view/fragment/DefaultStepQuizFragment.kt | 39 ++++++-------- .../Modules/StepQuiz/StepQuizAssembly.swift | 5 -- .../Modules/StepQuiz/StepQuizViewModel.swift | 15 ------ .../Modules/StepQuiz/Views/StepQuizView.swift | 7 --- .../step_quiz/AndroidStepQuizTest.kt | 54 ++----------------- .../domain/model/ProblemsLimitScreen.kt | 4 +- .../HyperskillSentryTransactionBuilder.kt | 6 --- .../injection/StepQuizComponentImpl.kt | 7 --- .../injection/StepQuizFeatureBuilder.kt | 21 ++------ .../step_quiz/presentation/StepQuizFeature.kt | 18 +------ .../step_quiz/presentation/StepQuizReducer.kt | 46 +--------------- .../org/hyperskill/step_quiz/StepQuizTest.kt | 38 +++++-------- 12 files changed, 38 insertions(+), 222 deletions(-) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 5479a5c4ad..85a6fe402c 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -43,8 +43,6 @@ import org.hyperskill.app.android.step_quiz.view.model.StepQuizFeedbackState import org.hyperskill.app.android.step_quiz_hints.fragment.StepQuizHintsFragment import org.hyperskill.app.android.step_quiz_parsons.view.dialog.ParsonsStepQuizOnboardingBottomSheetDialogFragment import org.hyperskill.app.android.view.base.ui.extension.snackbar -import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature import org.hyperskill.app.problems_limit.view.mapper.ProblemsLimitViewStateMapper import org.hyperskill.app.step.domain.model.BlockName import org.hyperskill.app.step.domain.model.Step @@ -82,6 +80,7 @@ abstract class DefaultStepQuizFragment : private var stepQuizStateDelegate: ViewStateDelegate? = null + // TODO: ALTAPPS-950 delete problems limit UI private var problemsLimitDelegate: ProblemsLimitDelegate? = null private var problemsLimitViewStateMapper: ProblemsLimitViewStateMapper? = null @@ -115,11 +114,9 @@ abstract class DefaultStepQuizFragment : private fun injectComponent() { val stepQuizComponent = HyperskillApp.graph().buildStepQuizComponent(stepRoute) val platformStepQuizComponent = HyperskillApp.graph().buildPlatformStepQuizComponent(stepQuizComponent) - val problemsLimitComponent = HyperskillApp.graph().buildProblemsLimitComponent(ProblemsLimitScreen.STEP_QUIZ) stepQuizStatsTextMapper = stepQuizComponent.stepQuizStatsTextMapper stepQuizTitleMapper = stepQuizComponent.stepQuizTitleMapper viewModelFactory = platformStepQuizComponent.reduxViewModelFactory - problemsLimitViewStateMapper = problemsLimitComponent.problemsLimitViewStateMapper } final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -158,14 +155,15 @@ abstract class DefaultStepQuizFragment : stepQuizViewModel.onNewMessage(StepQuizFeature.Message.InitWithStep(step)) } + // TODO: ALTAPPS-950 delete problems limit UI private fun initProblemsLimitDelegate() { - problemsLimitDelegate = ProblemsLimitDelegate( - viewBinding = viewBinding.stepQuizProblemsLimit, - onNewMessage = { - stepQuizViewModel.onNewMessage(StepQuizFeature.Message.ProblemsLimitMessage(it)) - } - ) - problemsLimitDelegate?.setup() +// problemsLimitDelegate = ProblemsLimitDelegate( +// viewBinding = viewBinding.stepQuizProblemsLimit, +// onNewMessage = { +// stepQuizViewModel.onNewMessage(StepQuizFeature.Message.ProblemsLimitMessage(it)) +// } +// ) +// problemsLimitDelegate?.setup() } private fun renderStatistics(textView: TextView, step: Step) { @@ -294,12 +292,6 @@ abstract class DefaultStepQuizFragment : ProblemsLimitReachedBottomSheet.newInstance(action.modalText) .showIfNotExists(childFragmentManager, ProblemsLimitReachedBottomSheet.TAG) } - is StepQuizFeature.Action.ViewAction.ProblemsLimitViewAction -> - when (action.viewAction) { - else -> { - // no op - } - } StepQuizFeature.Action.ViewAction.ShowParsonsProblemOnboardingModal -> { ParsonsStepQuizOnboardingBottomSheetDialogFragment.newInstance() .showIfNotExists(childFragmentManager, ParsonsStepQuizOnboardingBottomSheetDialogFragment.TAG) @@ -342,12 +334,13 @@ abstract class DefaultStepQuizFragment : setStepHintsFragment(step) renderAttemptLoaded(stepQuizState) - problemsLimitViewStateMapper?.let { mapper -> - val problemsLimitViewState = mapper.mapState(state.problemsLimitState) - viewBinding.problemsLimitDivider.root.isVisible = - problemsLimitViewState is ProblemsLimitFeature.ViewState.Content.Widget - problemsLimitDelegate?.render(problemsLimitViewState) - } + // TODO: ALTAPPS-950 delete problems limit UI +// problemsLimitViewStateMapper?.let { mapper -> +// val problemsLimitViewState = mapper.mapState(state.problemsLimitState) +// viewBinding.problemsLimitDivider.root.isVisible = +// problemsLimitViewState is ProblemsLimitFeature.ViewState.Content.Widget +// problemsLimitDelegate?.render(problemsLimitViewState) +// } } else -> { // no op diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizAssembly.swift index aed201d495..0b93a24999 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizAssembly.swift @@ -28,17 +28,12 @@ final class StepQuizAssembly: Assembly { stepQuizTitleMapper: stepQuizComponent.stepQuizTitleMapper ) - let problemsLimitComponent = AppGraphBridge.sharedAppGraph.buildProblemsLimitComponent( - screen: ProblemsLimitScreen.stepQuiz - ) - let viewModel = StepQuizViewModel( step: step, stepRoute: stepRoute, moduleOutput: moduleOutput, provideModuleInputCallback: provideModuleInputCallback, viewDataMapper: viewDataMapper, - problemsLimitViewStateMapper: problemsLimitComponent.problemsLimitViewStateMapper, feature: stepQuizComponent.stepQuizFeature ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift index 93eb266916..fb236d9f0f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift @@ -17,12 +17,7 @@ final class StepQuizViewModel: FeatureViewModel< private var updateChildQuizSubscription: AnyCancellable? private let stepQuizViewDataMapper: StepQuizViewDataMapper - private let problemsLimitViewStateMapper: ProblemsLimitViewStateMapper - var stepQuizStateKs: StepQuizFeatureStepQuizStateKs { .init(state.stepQuizState) } - var problemsLimitViewStateKs: ProblemsLimitFeatureViewStateKs { - .init(problemsLimitViewStateMapper.mapState(state: state.problemsLimitState)) - } @Published var isPracticingLoading = false @@ -32,7 +27,6 @@ final class StepQuizViewModel: FeatureViewModel< moduleOutput: StepQuizOutputProtocol?, provideModuleInputCallback: @escaping (StepQuizInputProtocol?) -> Void, viewDataMapper: StepQuizViewDataMapper, - problemsLimitViewStateMapper: ProblemsLimitViewStateMapper, feature: Presentation_reduxFeature ) { self.step = step @@ -40,7 +34,6 @@ final class StepQuizViewModel: FeatureViewModel< self.moduleOutput = moduleOutput self.provideModuleInputCallback = provideModuleInputCallback self.stepQuizViewDataMapper = viewDataMapper - self.problemsLimitViewStateMapper = problemsLimitViewStateMapper super.init(feature: feature) @@ -134,14 +127,6 @@ final class StepQuizViewModel: FeatureViewModel< ) } - func doReloadProblemsLimit() { - onNewMessage( - StepQuizFeatureMessageProblemsLimitMessage( - message: ProblemsLimitFeatureMessageInitialize(forceUpdate: true) - ) - ) - } - func doTheoryToolbarAction() { onNewMessage(StepQuizFeatureMessageTheoryToolbarItemClicked()) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index 58fd129f07..e5a6ce5e61 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -128,11 +128,6 @@ struct StepQuizView: View { StepQuizStatsView(text: formattedStats) } - StepQuizProblemsLimitView( - stateKs: viewModel.problemsLimitViewStateKs, - onReloadButtonTap: viewModel.doReloadProblemsLimit - ) - buildQuizStatusView(state: state.stepQuizState, attemptLoadedState: attemptLoadedState) if let feedbackHintText { @@ -264,8 +259,6 @@ struct StepQuizView: View { presentProblemsLimitReachedModal(modalText: showProblemsLimitReachedModalViewAction.modalText) case .showParsonsProblemOnboardingModal: presentParsonsProblemOnboardingModal() - case .problemsLimitViewAction: - break case .navigateTo(let viewActionNavigateTo): switch StepQuizFeatureActionViewActionNavigateToKs(viewActionNavigateTo) { case .home: 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 df5bf262f8..bba0b616e8 100644 --- a/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt +++ b/shared/src/androidUnitTest/kotlin/org/hyperskill/step_quiz/AndroidStepQuizTest.kt @@ -1,9 +1,6 @@ package org.hyperskill.step_quiz import kotlin.test.assertEquals -import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitReducer import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.model.attempts.Attempt @@ -14,48 +11,6 @@ import org.hyperskill.step_quiz.domain.model.stub import org.junit.Test class AndroidStepQuizTest { - @Test - fun `Problems limit state initialized only for Learn StepRoute`() { - val step = Step.stub(id = 1) - - val initialState = StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Idle, - problemsLimitState = ProblemsLimitFeature.State.Idle - ) - - StepRoute::class.sealedSubclasses.forEach { stepRouteClass -> - val expectedProblemsLimitState = when (stepRouteClass) { - StepRoute.Learn::class -> ProblemsLimitFeature.State.Loading - else -> ProblemsLimitFeature.State.Idle - } - - val expectedState = StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = expectedProblemsLimitState - ) - - val reducer = StepQuizReducer( - stepRoute = when (stepRouteClass) { - StepRoute.Learn::class -> StepRoute.Learn.Step(step.id) - StepRoute.LearnDaily::class -> StepRoute.LearnDaily(step.id) - StepRoute.Repeat::class -> StepRoute.Repeat.Practice(step.id) - StepRoute.StageImplement::class -> StepRoute.StageImplement(step.id, 1, 1) - else -> throw IllegalStateException( - "Unknown step route class: $stepRouteClass. Please add it to the test." - ) - }, - problemsLimitReducer = ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ) - ) - - val (state, _) = reducer.reduce( - initialState, - StepQuizFeature.Message.InitWithStep(step) - ) - - assertEquals(expectedState, state) - } - } - @Test fun `Theory should be available for Learn and Repeat StepRoutes`() { val step = Step.stub(id = 1, topicTheory = 2) @@ -83,8 +38,7 @@ class AndroidStepQuizTest { "Unknown step route class: $concreteStepRouteClass. Please add it to the test." ) } - ), - problemsLimitState = ProblemsLimitFeature.State.Idle + ) ) val reducer = StepQuizReducer( @@ -99,14 +53,12 @@ class AndroidStepQuizTest { else -> throw IllegalStateException( "Unknown step route class: $concreteStepRouteClass. Please add it to the test." ) - }, - ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ) + } ) val (state, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Loading ), StepQuizFeature.Message.FetchAttemptSuccess( step, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt index 7b40e856e3..96801e50d0 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/problems_limit/domain/model/ProblemsLimitScreen.kt @@ -5,13 +5,11 @@ import org.hyperskill.app.sentry.domain.model.transaction.HyperskillSentryTransa enum class ProblemsLimitScreen { HOME, - STUDY_PLAN, - STEP_QUIZ; + STUDY_PLAN; internal val sentryTransaction: HyperskillSentryTransaction get() = when (this) { HOME -> HyperskillSentryTransactionBuilder.buildProblemsLimitHomeScreenRemoteDataLoading() STUDY_PLAN -> HyperskillSentryTransactionBuilder.buildProblemsLimitStudyPlanScreenRemoteDataLoading() - STEP_QUIZ -> HyperskillSentryTransactionBuilder.buildProblemsLimitStepQuizScreenRemoteDataLoading() } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt index 93e11ab418..1b9590534e 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/sentry/domain/model/transaction/HyperskillSentryTransactionBuilder.kt @@ -200,12 +200,6 @@ object HyperskillSentryTransactionBuilder { operation = HyperskillSentryTransactionOperation.API_LOAD ) - fun buildProblemsLimitStepQuizScreenRemoteDataLoading(): HyperskillSentryTransaction = - HyperskillSentryTransaction( - name = "problems-limit-feature-step-quiz-screen-remote-data-loading", - operation = HyperskillSentryTransactionOperation.API_LOAD - ) - /** * StudyPlanWidgetFeature */ 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 08745999f2..be2c8e1b11 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 @@ -1,8 +1,6 @@ package org.hyperskill.app.step_quiz.injection import org.hyperskill.app.core.injection.AppGraph -import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen -import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponent import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.data.repository.AttemptRepositoryImpl import org.hyperskill.app.step_quiz.data.source.AttemptRemoteDataSource @@ -43,14 +41,9 @@ class StepQuizComponentImpl( appGraph.submissionDataComponent.submissionRepository ) - private val problemsLimitComponent: ProblemsLimitComponent = - appGraph.buildProblemsLimitComponent(ProblemsLimitScreen.STEP_QUIZ) - override val stepQuizFeature: Feature get() = StepQuizFeatureBuilder.build( stepRoute, - problemsLimitComponent.problemsLimitReducer, - problemsLimitComponent.problemsLimitActionDispatcher, stepQuizInteractor, stepQuizReplyValidator, appGraph.profileDataComponent.currentProfileStateRepository, 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 c957694b37..92cb4be952 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 @@ -5,9 +5,6 @@ import org.hyperskill.app.core.presentation.ActionDispatcherOptions import org.hyperskill.app.core.view.mapper.ResourceProvider import org.hyperskill.app.freemium.domain.interactor.FreemiumInteractor import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitActionDispatcher -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitReducer import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository import org.hyperskill.app.sentry.domain.interactor.SentryInteractor import org.hyperskill.app.step.domain.model.StepRoute @@ -16,8 +13,6 @@ import org.hyperskill.app.step_quiz.domain.validation.StepQuizReplyValidator import org.hyperskill.app.step_quiz.presentation.StepQuizActionDispatcher import org.hyperskill.app.step_quiz.presentation.StepQuizFeature import org.hyperskill.app.step_quiz.presentation.StepQuizReducer -import ru.nobird.app.core.model.safeCast -import ru.nobird.app.presentation.redux.dispatcher.transform import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher import ru.nobird.app.presentation.redux.feature.Feature import ru.nobird.app.presentation.redux.feature.ReduxFeature @@ -25,8 +20,6 @@ import ru.nobird.app.presentation.redux.feature.ReduxFeature object StepQuizFeatureBuilder { fun build( stepRoute: StepRoute, - problemsLimitReducer: ProblemsLimitReducer, - problemsLimitActionDispatcher: ProblemsLimitActionDispatcher, stepQuizInteractor: StepQuizInteractor, stepQuizReplyValidator: StepQuizReplyValidator, currentProfileStateRepository: CurrentProfileStateRepository, @@ -36,7 +29,7 @@ object StepQuizFeatureBuilder { onboardingInteractor: OnboardingInteractor, resourceProvider: ResourceProvider ): Feature { - val stepQuizReducer = StepQuizReducer(stepRoute, problemsLimitReducer) + val stepQuizReducer = StepQuizReducer(stepRoute) val stepQuizActionDispatcher = StepQuizActionDispatcher( ActionDispatcherOptions(), stepQuizInteractor, @@ -51,17 +44,9 @@ object StepQuizFeatureBuilder { return ReduxFeature( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Idle, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Idle ), stepQuizReducer - ) - .wrapWithActionDispatcher(stepQuizActionDispatcher) - .wrapWithActionDispatcher( - problemsLimitActionDispatcher.transform( - transformAction = { it.safeCast()?.action }, - transformMessage = StepQuizFeature.Message::ProblemsLimitMessage - ) - ) + ).wrapWithActionDispatcher(stepQuizActionDispatcher) } } \ 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 164f8ba4b4..2dc93d192d 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 @@ -1,7 +1,6 @@ package org.hyperskill.app.step_quiz.presentation import org.hyperskill.app.analytic.domain.model.AnalyticEvent -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepContext import org.hyperskill.app.step.domain.model.StepRoute @@ -12,8 +11,7 @@ import org.hyperskill.app.step_quiz.domain.validation.ReplyValidationResult interface StepQuizFeature { data class State( - val stepQuizState: StepQuizState, - val problemsLimitState: ProblemsLimitFeature.State + val stepQuizState: StepQuizState ) sealed interface StepQuizState { @@ -107,11 +105,6 @@ interface StepQuizFeature { object ProblemsLimitReachedModalShownEventMessage : Message object ProblemsLimitReachedModalHiddenEventMessage : Message object ParsonsProblemOnboardingModalHiddenEventMessage : Message - - /** - * Message Wrappers - */ - data class ProblemsLimitMessage(val message: ProblemsLimitFeature.Message) : Message } sealed interface Action { @@ -140,11 +133,6 @@ interface StepQuizFeature { */ data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : Action - /** - * Action Wrappers - */ - data class ProblemsLimitAction(val action: ProblemsLimitFeature.Action) : Action - sealed interface ViewAction : Action { object ShowNetworkError : ViewAction // error @@ -154,10 +142,6 @@ interface StepQuizFeature { object ShowParsonsProblemOnboardingModal : ViewAction - data class ProblemsLimitViewAction( - val viewAction: ProblemsLimitFeature.Action.ViewAction - ) : ViewAction - sealed interface NavigateTo : ViewAction { object Home : NavigateTo diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt index 317ccb45d7..919a8e848f 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 @@ -1,8 +1,6 @@ package org.hyperskill.app.step_quiz.presentation import kotlinx.datetime.Clock -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitReducer import org.hyperskill.app.step.domain.model.BlockName import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.analytic.ParsonsProblemOnboardingModalHiddenHyperskillAnalyticEvent @@ -28,8 +26,7 @@ import ru.nobird.app.presentation.redux.reducer.StateReducer internal typealias StepQuizReducerResult = Pair> class StepQuizReducer( - private val stepRoute: StepRoute, - private val problemsLimitReducer: ProblemsLimitReducer + private val stepRoute: StepRoute ) : StateReducer { override fun reduce(state: State, message: Message): StepQuizReducerResult = when (message) { @@ -243,12 +240,6 @@ class StepQuizReducer( ParsonsProblemOnboardingModalHiddenHyperskillAnalyticEvent(stepRoute.analyticRoute) ) ) - // Wrapper Messages - is Message.ProblemsLimitMessage -> { - val (problemsLimitState, problemsLimitActions) = - reduceProblemsLimitMessage(state.problemsLimitState, message.message) - state.copy(problemsLimitState = problemsLimitState) to problemsLimitActions - } } ?: (state to emptySet()) private fun handleFetchAttemptSuccess(state: State, message: Message.FetchAttemptSuccess): StepQuizReducerResult = @@ -287,26 +278,6 @@ class StepQuizReducer( state to emptySet() } - private fun reduceProblemsLimitMessage( - state: ProblemsLimitFeature.State, - message: ProblemsLimitFeature.Message - ): Pair> { - val (problemsLimitState, problemsLimitActions) = - problemsLimitReducer.reduce(state, message) - - val actions = problemsLimitActions - .map { - if (it is ProblemsLimitFeature.Action.ViewAction) { - Action.ViewAction.ProblemsLimitViewAction(it) - } else { - Action.ProblemsLimitAction(it) - } - } - .toSet() - - return problemsLimitState to actions - } - private fun initialize(state: State, message: Message.InitWithStep): StepQuizReducerResult { val needReloadStepQuiz = state.stepQuizState is StepQuizState.Idle || @@ -318,20 +289,7 @@ class StepQuizReducer( state.stepQuizState to emptySet() } - val (problemsLimitState, problemsLimitActions) = - if (stepRoute is StepRoute.Learn) { - reduceProblemsLimitMessage( - state.problemsLimitState, - ProblemsLimitFeature.Message.Initialize(message.forceUpdate) - ) - } else { - state.problemsLimitState to emptySet() - } - - return state.copy( - stepQuizState = stepQuizState, - problemsLimitState = problemsLimitState - ) to stepQuizActions + problemsLimitActions + return state.copy(stepQuizState = stepQuizState) to stepQuizActions } private fun handleTheoryToolbarItemClicked(state: State): StepQuizReducerResult = diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt index 34ac4662db..03bb9f1628 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt @@ -3,9 +3,6 @@ package org.hyperskill.step_quiz import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitFeature -import org.hyperskill.app.problems_limit.presentation.ProblemsLimitReducer import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbarItemHyperskillAnalyticEvent @@ -30,16 +27,14 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = false - ), - problemsLimitState = ProblemsLimitFeature.State.Idle + ) ) stepRoutes.forEach { stepRoute -> - val reducer = StepQuizReducer(stepRoute, ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ)) + val reducer = StepQuizReducer(stepRoute) val (state, actions) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Loading ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -69,18 +64,15 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = true, isTheoryAvailable = false - ), - problemsLimitState = ProblemsLimitFeature.State.Idle + ) ) val reducer = StepQuizReducer( - stepRoute = StepRoute.Learn.Step(step.id), - problemsLimitReducer = ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ) + stepRoute = StepRoute.Learn.Step(step.id) ) val (state, actions) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Loading ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -112,19 +104,16 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = true - ), - problemsLimitState = ProblemsLimitFeature.State.Idle + ) ) val reducer = StepQuizReducer( - StepRoute.Learn.Step(step.id), - ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ) + StepRoute.Learn.Step(step.id) ) val (intermediateState, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Loading ), StepQuizFeature.Message.FetchAttemptSuccess( step, @@ -170,19 +159,16 @@ class StepQuizTest { submissionState = submissionState, isProblemsLimitReached = false, isTheoryAvailable = false - ), - problemsLimitState = ProblemsLimitFeature.State.Idle + ) ) val reducer = StepQuizReducer( - StepRoute.LearnDaily(step.id), - ProblemsLimitReducer(ProblemsLimitScreen.STEP_QUIZ) + StepRoute.LearnDaily(step.id) ) val (intermediateState, _) = reducer.reduce( StepQuizFeature.State( - stepQuizState = StepQuizFeature.StepQuizState.Loading, - problemsLimitState = ProblemsLimitFeature.State.Idle + stepQuizState = StepQuizFeature.StepQuizState.Loading ), StepQuizFeature.Message.FetchAttemptSuccess( step, From 33ac6f5af278f706187888a52611e8adac380711 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 13:26:13 +0400 Subject: [PATCH 07/16] Update preview code editor view --- .../project.pbxproj | 4 + .../Contents.json | 15 ++++ .../step-quiz-code-editor-expand.pdf | Bin 0 -> 2834 bytes .../Models/Constants/Images/Images.swift | 1 + .../Sources/Models/Constants/Strings.swift | 2 + .../StepQuizCode/StepQuizCodeViewModel.swift | 14 +-- .../Views/StepQuizCodeEditorView.swift | 83 ++++++++++++++++++ .../StepQuizCode/Views/StepQuizCodeView.swift | 28 ++---- .../StepQuizPyCharm/StepQuizPyCharmView.swift | 28 ++---- .../StepQuizSQL/Views/StepQuizSQLView.swift | 28 ++---- .../commonMain/resources/MR/base/strings.xml | 1 + 11 files changed, 139 insertions(+), 65 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/Contents.json create mode 100644 iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/step-quiz-code-editor-expand.pdf create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d68acafc40..d6e002e7e1 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -197,6 +197,7 @@ 2C5CBBE32948F4B600113007 /* StepQuizSQLViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5CBBE22948F4B600113007 /* StepQuizSQLViewModel.swift */; }; 2C5CBBE52948FA7400113007 /* StepQuizSQLAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5CBBE42948FA7400113007 /* StepQuizSQLAssembly.swift */; }; 2C5CBBE72948FC7A00113007 /* StepQuizSQLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5CBBE62948FC7A00113007 /* StepQuizSQLView.swift */; }; + 2C5EC2C82AC41CAF0098D126 /* StepQuizCodeEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5EC2C72AC41CAF0098D126 /* StepQuizCodeEditorView.swift */; }; 2C5F4A5A2971C71200677530 /* GamificationToolbarContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5F4A592971C71200677530 /* GamificationToolbarContent.swift */; }; 2C62AD582AB43A8F00F3DD5B /* BadgeRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C62AD572AB43A8F00F3DD5B /* BadgeRankView.swift */; }; 2C6672062A527C0D0040EA2F /* ProgressScreenSectionTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6672052A527C0D0040EA2F /* ProgressScreenSectionTitleView.swift */; }; @@ -795,6 +796,7 @@ 2C5CBBE22948F4B600113007 /* StepQuizSQLViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizSQLViewModel.swift; sourceTree = ""; }; 2C5CBBE42948FA7400113007 /* StepQuizSQLAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizSQLAssembly.swift; sourceTree = ""; }; 2C5CBBE62948FC7A00113007 /* StepQuizSQLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizSQLView.swift; sourceTree = ""; }; + 2C5EC2C72AC41CAF0098D126 /* StepQuizCodeEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeEditorView.swift; sourceTree = ""; }; 2C5F4A592971C71200677530 /* GamificationToolbarContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamificationToolbarContent.swift; sourceTree = ""; }; 2C62AD572AB43A8F00F3DD5B /* BadgeRankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeRankView.swift; sourceTree = ""; }; 2C6672052A527C0D0040EA2F /* ProgressScreenSectionTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressScreenSectionTitleView.swift; sourceTree = ""; }; @@ -2352,6 +2354,7 @@ 2C967435288829E40091B6C9 /* Views */ = { isa = PBXGroup; children = ( + 2C5EC2C72AC41CAF0098D126 /* StepQuizCodeEditorView.swift */, 2CA3B0352888955F00EEF716 /* StepQuizCodeSkeletonView.swift */, 2C967433288824450091B6C9 /* StepQuizCodeView.swift */, 2C96743828882A130091B6C9 /* Details */, @@ -4187,6 +4190,7 @@ 2C7994B12A129D6100874C16 /* TrackSelectionListSkeletonView.swift in Sources */, 2C1F5875280D0E0000372A37 /* WKWebViewPanelManager.m in Sources */, 2CDF14D228EEF9690060D972 /* AppTabBarController.swift in Sources */, + 2C5EC2C82AC41CAF0098D126 /* StepQuizCodeEditorView.swift in Sources */, 2C1F5870280D0CB700372A37 /* WebCacheCleaner.swift in Sources */, E9D2D673284E0A97000757AC /* StepQuizMatchingItemView.swift in Sources */, E94BB04E2A9E034700736B7C /* StepQuizParsonsAssembly.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/Contents.json b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/Contents.json new file mode 100644 index 0000000000..c73208e07c --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "step-quiz-code-editor-expand.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/step-quiz-code-editor-expand.pdf b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/step-quiz-code-editor-expand.pdf new file mode 100644 index 0000000000000000000000000000000000000000..698445bdec1631f603eb358e7bf43d8d9f8c63e6 GIT binary patch literal 2834 zcmZveO>f*b5Qgvk6}%Wo4w3kwPap`;*iBI+LEUw4K@VG5vA5Wj+SMjV^6T@Gni*1Y zbTH^g;>pUr0%Ho)q zY}hc$QnA>UeXLk&%TqoeR-Ellx9j6x`*pM(2)X51mykih%JnT0=6PtcNHi!}KFwT!&5@b0uQl13!cKqiHKBqlSG-kOYv4;iCAOQfuol#d#2QM0I`vG8oS(=u5y zPC^Fts4PA{k#i#5hmm$eTtpufzK_IK) z#6$X_N**0IP%7c#@N>cSMiWf<@gZMAyl7SxAo(XH0dYfQAt*Y@`h>y|F*DmPo@ZI4 zIbb3ubrJQE?yT80H)_L3MVuk&n=}g}l%Nb&=o}aKQ^zt7lh%&HUC+~HoRBdbvl!m)&tMwE8&P%`3rG%W<=ewOr0P(? zPII2?`4jfqC;AtVEE2{zixdi6Bw;H2dVkp;=ZEdqFX!>2GP+f({o$`}>Kbn>FkKdJ zRk-vd$V2A_IoF{lTjULdZBd1T*AEEE1YOSi!*M(-oLbA@Z(zr>Wj>A1^|QnM^VO7c iH_sOx3XTSscZY8y)W_57{(On$xW+nlb@l5XZ@vRStvjLs literal 0 HcmV?d00001 diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift index 41014f4aee..d25080b8fb 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift @@ -65,6 +65,7 @@ enum Images { static let discussions = "step-quiz-discussions" static let info = "step-quiz-info" static let lightning = "step-quiz-lightning" + static let expand = "step-quiz-code-editor-expand" enum Hints { static let helpfulReaction = "step_quiz_hints_helpful_reaction" diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift index ff5bb5159d..f4dc7a11b0 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift @@ -154,6 +154,8 @@ enum Strings { static let fullScreenCodeTab = sharedStrings.step_quiz_code_full_screen_code_tab.localized() static let emptyLang = sharedStrings.step_quiz_code_empty_lang.localized() static let reset = sharedStrings.step_quiz_code_reset.localized() + + static let codeEditorTitle = sharedStrings.step_quiz_code_editor_title.localized() } // MARK: - StepQuizSQL - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift index 196009991d..17d16d328d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift @@ -47,6 +47,14 @@ class StepQuizCodeViewModel: ObservableObject { func logClickedCodeDetailsEvent() { moduleOutput?.handleChildQuizAnalyticEventMessage(StepQuizFeatureMessageClickedCodeDetailsEventMessage()) } + + func handleCodeDidChange(_ newCode: String?) { + viewData.code = newCode + + DispatchQueue.main.async { + self.syncReply(code: newCode) + } + } } // MARK: - StepQuizCodeViewModel: StepQuizChildQuizInputProtocol - @@ -85,11 +93,7 @@ extension StepQuizCodeViewModel: StepQuizChildQuizInputProtocol { extension StepQuizCodeViewModel: StepQuizCodeFullScreenOutputProtocol { func handleStepQuizCodeFullScreenUpdatedCode(_ code: String?) { - viewData.code = code - - DispatchQueue.main.async { - self.syncReply(code: code) - } + handleCodeDidChange(code) } func handleStepQuizCodeFullScreenRetryRequested() { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift new file mode 100644 index 0000000000..bb49334921 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift @@ -0,0 +1,83 @@ +import SwiftUI + +extension StepQuizCodeEditorView { + struct Appearance { + let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) + let codeEditorMinHeightHeight: CGFloat = 300 + } +} + +struct StepQuizCodeEditorView: View { + private(set) var appearance = Appearance() + + @Binding var code: String? + let codeTemplate: String? + + let language: CodeLanguage? + + let onExpandButtonTap: () -> Void + + @Environment(\.isEnabled) private var isEnabled + + var body: some View { + VStack(spacing: 0) { + Divider() + + HStack { + Text(Strings.StepQuizCode.codeEditorTitle) + .font(.headline) + .foregroundColor(.primaryText) + + if let languageName = language?.humanReadableName { + Text("(\(languageName))") + .font(.subheadline) + .foregroundColor(.tertiaryText) + } + + Spacer() + + Button( + action: onExpandButtonTap, + label: { + Image(Images.StepQuiz.expand) + .renderingMode(.template) + .font(.headline) + .foregroundColor(.secondaryText) + .aspectRatio(contentMode: .fit) + .opacity(isEnabled ? 1 : 0.38) + } + ) + } + .padding(.horizontal) + .padding(.vertical, LayoutInsets.smallInset) + .background(BackgroundView()) + + Divider() + + CodeEditor( + code: $code, + codeTemplate: codeTemplate, + language: language, + isEditable: true, + textInsets: appearance.codeEditorInsets.uiEdgeInsets + ) + .frame( + maxWidth: .infinity, + minHeight: appearance.codeEditorMinHeightHeight + ) + + Divider() + } + } +} + +struct StepQuizCodeEditorView_Previews: PreviewProvider { + static var previews: some View { + StepQuizCodeEditorView( + code: .constant(CodeLanguageSamples.sample(for: .java)), + codeTemplate: nil, + language: .java, + onExpandButtonTap: {} + ) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index ee7ade3271..15977fe420 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -1,15 +1,6 @@ import SwiftUI -extension StepQuizCodeView { - struct Appearance { - let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) - let codeEditorHeight: CGFloat = 128 - } -} - struct StepQuizCodeView: View { - private(set) var appearance = Appearance() - @StateObject var viewModel: StepQuizCodeViewModel @Environment(\.isEnabled) private var isEnabled @@ -23,21 +14,18 @@ struct StepQuizCodeView: View { onExpandTapped: viewModel.logClickedCodeDetailsEvent ) - CodeEditor( - code: .constant(viewData.code), + StepQuizCodeEditorView( + code: Binding( + get: { viewModel.viewData.code }, + set: { viewModel.handleCodeDidChange($0) } + ), codeTemplate: viewData.codeTemplate, language: viewData.language, - isEditable: false, - textInsets: appearance.codeEditorInsets.uiEdgeInsets - ) - .frame(height: appearance.codeEditorHeight) - .frame(maxWidth: .infinity) - .addBorder() - .onTapGesture { - if isEnabled { + onExpandButtonTap: { viewModel.navigationState.presentingFullScreen = true } - } + ) + .padding(.horizontal, -LayoutInsets.defaultInset) } .fullScreenCover(isPresented: $viewModel.navigationState.presentingFullScreen) { StepQuizCodeFullScreenAssembly( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift index 7503a81ad0..e7284f72cb 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift @@ -1,15 +1,6 @@ import SwiftUI -extension StepQuizPyCharmView { - struct Appearance { - let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) - let codeEditorHeight: CGFloat = 128 - } -} - struct StepQuizPyCharmView: View { - private(set) var appearance = Appearance() - @StateObject var viewModel: StepQuizPyCharmViewModel @Environment(\.isEnabled) private var isEnabled @@ -18,21 +9,18 @@ struct StepQuizPyCharmView: View { VStack(alignment: .leading, spacing: LayoutInsets.defaultInset) { let viewData = viewModel.viewData - CodeEditor( - code: .constant(viewData.code), + StepQuizCodeEditorView( + code: Binding( + get: { viewModel.viewData.code }, + set: { viewModel.handleCodeDidChange($0) } + ), codeTemplate: viewData.codeTemplate, language: viewData.language, - isEditable: false, - textInsets: appearance.codeEditorInsets.uiEdgeInsets - ) - .frame(height: appearance.codeEditorHeight) - .frame(maxWidth: .infinity) - .addBorder() - .onTapGesture { - if isEnabled { + onExpandButtonTap: { viewModel.navigationState.presentingFullScreen = true } - } + ) + .padding(.horizontal, -LayoutInsets.defaultInset) } .fullScreenCover(isPresented: $viewModel.navigationState.presentingFullScreen) { StepQuizCodeFullScreenAssembly( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift index 04a74e2fae..9f6002e45b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift @@ -1,15 +1,6 @@ import SwiftUI -extension StepQuizSQLView { - struct Appearance { - let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) - let codeEditorHeight: CGFloat = 128 - } -} - struct StepQuizSQLView: View { - private(set) var appearance = Appearance() - @StateObject var viewModel: StepQuizSQLViewModel @Environment(\.isEnabled) private var isEnabled @@ -18,21 +9,18 @@ struct StepQuizSQLView: View { VStack(alignment: .leading, spacing: LayoutInsets.defaultInset) { let viewData = viewModel.viewData - CodeEditor( - code: .constant(viewData.code), + StepQuizCodeEditorView( + code: Binding( + get: { viewModel.viewData.code }, + set: { viewModel.handleCodeDidChange($0) } + ), codeTemplate: viewData.codeTemplate, language: viewData.language, - isEditable: false, - textInsets: appearance.codeEditorInsets.uiEdgeInsets - ) - .frame(height: appearance.codeEditorHeight) - .frame(maxWidth: .infinity) - .addBorder() - .onTapGesture { - if isEnabled { + onExpandButtonTap: { viewModel.navigationState.presentingFullScreen = true } - } + ) + .padding(.horizontal, -LayoutInsets.defaultInset) } .fullScreenCover(isPresented: $viewModel.navigationState.presentingFullScreen) { StepQuizCodeFullScreenAssembly( diff --git a/shared/src/commonMain/resources/MR/base/strings.xml b/shared/src/commonMain/resources/MR/base/strings.xml index 94f2645842..2f42904cb6 100644 --- a/shared/src/commonMain/resources/MR/base/strings.xml +++ b/shared/src/commonMain/resources/MR/base/strings.xml @@ -150,6 +150,7 @@ Code No lang Reset + Code editor Write an SQL statement From 3bd2a9ebae0bce1d7d3932f785b57af35eb157e5 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 15:51:01 +0400 Subject: [PATCH 08/16] Auto growing code editor --- .../CodeEditor/View/SwiftUI/CodeEditor.swift | 10 +++++++ .../UIKit/CodeEditorView/CodeEditorView.swift | 8 ++++-- .../CodeEditorViewDelegate.swift | 1 + .../UIKit/CodeTextView/CodeTextView.swift | 26 +++++++++++++++++++ .../Sources/Modules/Step/StepAssembly.swift | 1 + .../Views/StepQuizCodeEditorView.swift | 23 +++++++++++----- .../Sources/Systems/KeyboardManager.swift | 4 +++ 7 files changed, 65 insertions(+), 8 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift index c1a2713ea7..9a9c597101 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift @@ -28,6 +28,8 @@ struct CodeEditor: UIViewRepresentable { var onDidEndEditing: (() -> Void)? + var onDidChangeHeight: ((CGFloat) -> Void)? + // MARK: UIViewRepresentable static func dismantleUIView(_ uiView: CodeEditorView, coordinator: Coordinator) { @@ -36,6 +38,7 @@ struct CodeEditor: UIViewRepresentable { coordinator.onCodeDidChange = nil coordinator.onDidBeginEditing = nil coordinator.onDidEndEditing = nil + coordinator.onDidChangeHeight = nil coordinator.suggestionsPresentationContextProvider = nil } @@ -89,6 +92,7 @@ struct CodeEditor: UIViewRepresentable { onDidEndEditing?() } + context.coordinator.onDidChangeHeight = onDidChangeHeight } } @@ -104,6 +108,8 @@ extension CodeEditor { var onDidEndEditing: (() -> Void)? + var onDidChangeHeight: ((CGFloat) -> Void)? + init(suggestionsPresentationContextProvider: CodeEditorSuggestionsPresentationContextProviding?) { self.suggestionsPresentationContextProvider = suggestionsPresentationContextProvider } @@ -127,6 +133,10 @@ extension CodeEditor { ) -> UIViewController? { suggestionsPresentationContextProvider?.presentationController(for: codeEditorView) } + + func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat) { + onDidChangeHeight?(height) + } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift index 367ad659af..bcfa8c4928 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift @@ -198,9 +198,13 @@ extension CodeEditorView: ProgrammaticallyInitializableViewProtocol { } } -// MARK: - CodeEditorView: UITextViewDelegate - +// MARK: - CodeEditorView: CodeTextViewDelegate - + +extension CodeEditorView: CodeTextViewDelegate { + func codeTextViewDidChangeHeight(_ textView: CodeTextView, height: CGFloat) { + delegate?.codeEditorViewDidChangeHeight(self, height: height) + } -extension CodeEditorView: UITextViewDelegate { func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { delegate?.codeEditorView(self, beginEditing: isEditable) return isEditable diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift index 1a001d82cf..36a317108a 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift @@ -6,6 +6,7 @@ protocol CodeEditorViewDelegate: AnyObject { func codeEditorViewDidBeginEditing(_ codeEditorView: CodeEditorView) func codeEditorViewDidEndEditing(_ codeEditorView: CodeEditorView) func codeEditorViewDidRequestSuggestionPresentationController(_ codeEditorView: CodeEditorView) -> UIViewController? + func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat) } extension CodeEditorViewDelegate { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift index 725e836688..33e951e877 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeTextView/CodeTextView.swift @@ -1,6 +1,10 @@ import Highlightr import UIKit +protocol CodeTextViewDelegate: UITextViewDelegate { + func codeTextViewDidChangeHeight(_ textView: CodeTextView, height: CGFloat) +} + extension CodeTextView { struct Appearance { var gutterWidth: CGFloat = 24 @@ -39,6 +43,10 @@ final class CodeTextView: UITextView { private lazy var codeTextViewLayoutManager = layoutManager as? CodeTextViewLayoutManager private lazy var codeAttributedString = textStorage as? CodeAttributedString + // Calculate textview's height + private var oldText: String = "" + private var oldSize: CGSize = .zero + var language: String? { didSet { guard language != oldValue, @@ -121,6 +129,7 @@ final class CodeTextView: UITextView { override func layoutSubviews() { super.layoutSubviews() codeTextViewLayoutManager?.appearance.currentLineWidth = bounds.width + calculateBestFitsSize() } override func draw(_ rect: CGRect) { @@ -231,6 +240,23 @@ final class CodeTextView: UITextView { return UIColor(red: 1.0 - r, green: 1.0 - g, blue: 1.0 - b, alpha: 1) } + + private func calculateBestFitsSize() { + guard bounds.size.width > 0, + let delegate = delegate as? CodeTextViewDelegate else { + return + } + + if text == oldText && bounds.size == oldSize { + return + } + + oldText = text + oldSize = bounds.size + + let size = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)) + delegate.codeTextViewDidChangeHeight(self, height: size.height) + } } // MARK: - CodeTextView: NSLayoutManagerDelegate - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/StepAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/StepAssembly.swift index 6190473d29..d7fc9a77c7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/StepAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/StepAssembly.swift @@ -42,6 +42,7 @@ final class StepAssembly: Assembly, UIKitAssembly { panModalPresenter: PanModalPresenter() ) let hostingController = StyledHostingController(rootView: stepView, appearance: .withoutBackButtonTitle) + hostingController.hidesBottomBarWhenPushed = true // Fixes an issue with that SwiftUI view content layout unexpectedly pop/jumps on appear hostingController.navigationItem.largeTitleDisplayMode = .never diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift index bb49334921..83ef803910 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift @@ -3,7 +3,7 @@ import SwiftUI extension StepQuizCodeEditorView { struct Appearance { let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) - let codeEditorMinHeightHeight: CGFloat = 300 + let codeEditorMinHeight: CGFloat = 300 } } @@ -19,6 +19,8 @@ struct StepQuizCodeEditorView: View { @Environment(\.isEnabled) private var isEnabled + @State private var height: CGFloat = 300 + var body: some View { VStack(spacing: 0) { Divider() @@ -59,12 +61,21 @@ struct StepQuizCodeEditorView: View { codeTemplate: codeTemplate, language: language, isEditable: true, - textInsets: appearance.codeEditorInsets.uiEdgeInsets - ) - .frame( - maxWidth: .infinity, - minHeight: appearance.codeEditorMinHeightHeight + textInsets: appearance.codeEditorInsets.uiEdgeInsets, + onDidChangeHeight: { newHeight in + let constrainMinimumHeight = max(newHeight, appearance.codeEditorMinHeight) + guard constrainMinimumHeight != height else { + return + } + + DispatchQueue.main.async { + height = constrainMinimumHeight + KeyboardManager.reloadLayoutIfNeeded() + } + } ) + .frame(height: height) + .frame(maxWidth: .infinity) Divider() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/KeyboardManager.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/KeyboardManager.swift index b51604953d..7e909788db 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Systems/KeyboardManager.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Systems/KeyboardManager.swift @@ -22,4 +22,8 @@ enum KeyboardManager { static func setEnabled(_ isEnabled: Bool) { IQKeyboardManager.shared.enable = isEnabled } + + static func reloadLayoutIfNeeded() { + IQKeyboardManager.shared.reloadLayoutIfNeeded() + } } From 6798b7e322a2e147d66b7961da099160a3bf6c3e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 17:55:21 +0400 Subject: [PATCH 09/16] Log analytic events --- .../project.pbxproj | 8 ++--- ...iew.swift => ExpandableStepTextView.swift} | 24 ++++++++------ .../Modules/StepQuiz/StepQuizViewModel.swift | 4 +++ .../Modules/StepQuiz/Views/StepQuizView.swift | 5 +-- .../StepQuizCode/StepQuizCodeViewModel.swift | 12 +++++++ .../Details/StepQuizCodeDetailsView.swift | 10 +++--- ...StepQuizCodeFullScreenOutputProtocol.swift | 3 ++ .../StepQuizCodeFullScreenViewModel.swift | 8 +++++ .../StepQuizCodeFullScreenDetailsView.swift | 15 ++++++--- .../Views/StepQuizCodeFullScreenView.swift | 4 ++- .../hyperskill/HyperskillAnalyticPart.kt | 3 +- .../hyperskill/HyperskillAnalyticTarget.kt | 1 + ...dStepTextDetailsHyperskillAnalyticEvent.kt | 31 ++++++++++++++++++ ...ickedCodeDetailsHyperskillAnalyticEvent.kt | 31 ++++++++++++++++++ ...dStepTextDetailsHyperskillAnalyticEvent.kt | 32 +++++++++++++++++++ .../step_quiz/presentation/StepQuizFeature.kt | 7 ++++ .../step_quiz/presentation/StepQuizReducer.kt | 31 ++++++++++++++++++ 17 files changed, 203 insertions(+), 26 deletions(-) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/{CollapsableStepTextView.swift => ExpandableStepTextView.swift} (75%) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedStepTextDetailsHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent.kt create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent.kt diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index d6e002e7e1..a8cf2a946f 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ 2C96743F288831BB0091B6C9 /* StepQuizCodeSamplesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96743E288831BB0091B6C9 /* StepQuizCodeSamplesView.swift */; }; 2C96744228883A180091B6C9 /* StepQuizCodeViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96744128883A180091B6C9 /* StepQuizCodeViewDataMapper.swift */; }; 2C96744428883E710091B6C9 /* BlockOptionsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */; }; - 2C971B852AC2F5DC00868FCE /* CollapsableStepTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */; }; + 2C971B852AC2F5DC00868FCE /* ExpandableStepTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C971B842AC2F5DC00868FCE /* ExpandableStepTextView.swift */; }; 2C975D662A1128670068FD4E /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */; }; 2C97B0CD298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */; }; 2C97E55A2859A2C500EA1A21 /* StepQuizNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */; }; @@ -873,7 +873,7 @@ 2C96743E288831BB0091B6C9 /* StepQuizCodeSamplesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeSamplesView.swift; sourceTree = ""; }; 2C96744128883A180091B6C9 /* StepQuizCodeViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeViewDataMapper.swift; sourceTree = ""; }; 2C96744328883E710091B6C9 /* BlockOptionsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockOptionsExtensions.swift; sourceTree = ""; }; - 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableStepTextView.swift; sourceTree = ""; }; + 2C971B842AC2F5DC00868FCE /* ExpandableStepTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableStepTextView.swift; sourceTree = ""; }; 2C975D652A1128670068FD4E /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; 2C97B0CC298124C1001DF1A0 /* StepQuizHintsFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizHintsFeatureViewStateKsExtensions.swift; sourceTree = ""; }; 2C97E5592859A2C500EA1A21 /* StepQuizNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizNameView.swift; sourceTree = ""; }; @@ -2393,7 +2393,7 @@ 2C971B832AC2F5A600868FCE /* StepText */ = { isa = PBXGroup; children = ( - 2C971B842AC2F5DC00868FCE /* CollapsableStepTextView.swift */, + 2C971B842AC2F5DC00868FCE /* ExpandableStepTextView.swift */, 2C9F59A629267A530008ADC5 /* StepTextView.swift */, ); path = StepText; @@ -4011,7 +4011,7 @@ 2CCCA3A12862E62F00D98089 /* StepQuizStringViewData.swift in Sources */, 2C1061A2285C349400EBD614 /* StepQuizChildQuizAssembly.swift in Sources */, 2C11D5CA2A11311900C59238 /* FeedbackGeneratorPreviewView.swift in Sources */, - 2C971B852AC2F5DC00868FCE /* CollapsableStepTextView.swift in Sources */, + 2C971B852AC2F5DC00868FCE /* ExpandableStepTextView.swift in Sources */, 2CB279AF28C72AA400EDDCC8 /* DeepLinkRouterProtocol.swift in Sources */, 2C023C86285D927A00D2D5A9 /* StepQuizTableAssembly.swift in Sources */, 2C20FBC4284F67F3006D879E /* ProcessedContentWebView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift similarity index 75% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift index f4f9132e09..04595d7c7f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/CollapsableStepTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift @@ -1,6 +1,6 @@ import SwiftUI -extension CollapsableStepTextView { +extension ExpandableStepTextView { struct Appearance { let spacing = LayoutInsets.defaultInset @@ -8,20 +8,23 @@ extension CollapsableStepTextView { } } -struct CollapsableStepTextView: View { +struct ExpandableStepTextView: View { private(set) var appearance = Appearance() var text: String - @State var isCollapsed = false + @State var isExpanded = true + + let onExpandButtonTap: () -> Void var body: some View { VStack(alignment: .center, spacing: appearance.spacing) { Button( action: { - #warning("log clicked event") + onExpandButtonTap() + withAnimation { - isCollapsed.toggle() + isExpanded.toggle() } }, label: { @@ -35,13 +38,13 @@ struct CollapsableStepTextView: View { Image(systemName: "chevron.right") .imageScale(.small) .aspectRatio(contentMode: .fit) - .rotationEffect(.radians(isCollapsed ? .zero : (.pi / 2))) + .rotationEffect(.radians(isExpanded ? (.pi / 2) : .zero)) } .font(.headline) } ) - if !isCollapsed { + if isExpanded { StepTextView( text: text, appearance: appearance.stepTextViewAppearance, @@ -52,12 +55,13 @@ struct CollapsableStepTextView: View { } } -struct CollapsableStepTextView_Previews: PreviewProvider { +struct ExpandableStepTextView_Previews: PreviewProvider { static var previews: some View { - CollapsableStepTextView( + ExpandableStepTextView( text: """

Despite the fact that the syntax for different databases may differ, most of them have common standards.

-""" +""", + onExpandButtonTap: {} ) .padding() .frame(height: 200) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift index fb236d9f0f..8d48b91bea 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift @@ -136,6 +136,10 @@ final class StepQuizViewModel: FeatureViewModel< private func logClickedRetryEvent() { onNewMessage(StepQuizFeatureMessageClickedRetryEventMessage()) } + + func logClickedStepTextDetailsEvent() { + onNewMessage(StepQuizFeatureMessageClickedStepTextDetailsEventMessage()) + } } // MARK: - StepQuizViewModel: StepQuizChildQuizDelegate - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift index e5a6ce5e61..ed87c01a4c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift @@ -54,9 +54,10 @@ struct StepQuizView: View { StepTextView(text: viewData.stepText) } else { if viewData.quizType.isCodeRelated { - CollapsableStepTextView( + ExpandableStepTextView( text: viewData.stepText, - isCollapsed: false + isExpanded: true, + onExpandButtonTap: viewModel.logClickedStepTextDetailsEvent ) } else { StepTextView(text: viewData.stepText) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift index 17d16d328d..cb72f518e9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift @@ -110,6 +110,18 @@ extension StepQuizCodeViewModel: StepQuizCodeFullScreenOutputProtocol { } } + func handleStepQuizCodeFullScreenToggledStepTextDetails() { + moduleOutput?.handleChildQuizAnalyticEventMessage( + StepQuizFeatureMessageFullScreenCodeEditorClickedStepTextDetailsEventMessage() + ) + } + + func handleStepQuizCodeFullScreenToggledCodeDetails() { + moduleOutput?.handleChildQuizAnalyticEventMessage( + StepQuizFeatureMessageFullScreenCodeEditorClickedCodeDetailsEventMessage() + ) + } + @objc func syncReply(code: String?) { let reply = Reply(language: viewData.languageStringValue, code: code) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift index ee33146733..d0ee743655 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift @@ -5,7 +5,7 @@ struct StepQuizCodeDetailsView: View { @State var isExpanded = false - var onExpandTapped: (() -> Void)? + var onExpandTapped: () -> Void var body: some View { if samples.isEmpty { @@ -19,7 +19,7 @@ struct StepQuizCodeDetailsView: View { VStack(alignment: .center, spacing: LayoutInsets.defaultInset) { Button( action: { - onExpandTapped?() + onExpandTapped() withAnimation { isExpanded.toggle() @@ -63,7 +63,8 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputValue: "true" ) ], - isExpanded: false + isExpanded: false, + onExpandTapped: {} ) StepQuizCodeDetailsView( @@ -75,7 +76,8 @@ struct StepQuizCodeDetailsView_Previews: PreviewProvider { outputValue: "true" ) ], - isExpanded: true + isExpanded: true, + onExpandTapped: {} ) } .previewLayout(.sizeThatFits) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift index 255c6bd2fc..5896edd8bc 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift @@ -4,4 +4,7 @@ protocol StepQuizCodeFullScreenOutputProtocol: AnyObject { func handleStepQuizCodeFullScreenUpdatedCode(_ code: String?) func handleStepQuizCodeFullScreenRetryRequested() func handleStepQuizCodeFullScreenSubmitRequested() + // Analytic + func handleStepQuizCodeFullScreenToggledStepTextDetails() + func handleStepQuizCodeFullScreenToggledCodeDetails() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift index 14081f9225..898f085946 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift @@ -40,6 +40,14 @@ final class StepQuizCodeFullScreenViewModel: ObservableObject { func doProvideModuleInput() { provideModuleInputCallback(self) } + + func logClickedStepTextDetailsEvent() { + moduleOutput?.handleStepQuizCodeFullScreenToggledStepTextDetails() + } + + func logClickedCodeDetailsEvent() { + moduleOutput?.handleStepQuizCodeFullScreenToggledCodeDetails() + } } // MARK: - StepQuizCodeFullScreenViewModel: StepQuizCodeFullScreenInputProtocol - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift index db616346a0..756143bdcf 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift @@ -16,17 +16,22 @@ struct StepQuizCodeFullScreenDetailsView: View { let samples: [StepQuizCodeViewData.Sample] + let onExpandStepTextButtonTap: () -> Void + let onExpandCodeDetailsButtonTap: () -> Void + var body: some View { ScrollView { VStack(alignment: .leading, spacing: appearance.spacing) { - CollapsableStepTextView( + ExpandableStepTextView( text: stepText, - isCollapsed: false + isExpanded: true, + onExpandButtonTap: onExpandStepTextButtonTap ) StepQuizCodeDetailsView( samples: samples, - isExpanded: true + isExpanded: true, + onExpandTapped: onExpandCodeDetailsButtonTap ) } .padding() @@ -47,7 +52,9 @@ Enter only the name of the found functional interface with/without the package. outputTitle: "Sample Output 1", outputValue: "true" ) - ] + ], + onExpandStepTextButtonTap: {}, + onExpandCodeDetailsButtonTap: {} ) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift index fbbc324475..d0ae09defd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift @@ -29,7 +29,9 @@ struct StepQuizCodeFullScreenView: View { TabNavigationLazyView( StepQuizCodeFullScreenDetailsView( stepText: viewData.stepText, - samples: viewData.samples + samples: viewData.samples, + onExpandStepTextButtonTap: viewModel.logClickedStepTextDetailsEvent, + onExpandCodeDetailsButtonTap: viewModel.logClickedCodeDetailsEvent ) ) .tag(StepQuizCodeFullScreenTab.details) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index 3ec1b24d2d..dc8cddf4d9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -32,5 +32,6 @@ enum class HyperskillAnalyticPart(val partName: String) { STREAK_RECOVERY_MODAL("streak_recovery_modal"), STAGE_COMPLETED_MODAL("stage_completed_modal"), PROJECT_COMPLETED_MODAL("project_completed_modal"), - NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget") + NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget"), + FULL_SCREEN_CODE_EDITOR("full_screen_code_editor") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index 79010f1d09..7c8bf09fca 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -8,6 +8,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) { DEBUG("debug"), SEND("send"), INPUT_OUTPUT_INFO("input_output_info"), + STEP_TEXT_DESCRIPTION("step_text_description"), RESET("reset"), RUN("run"), ALLOW("allow"), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedStepTextDetailsHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedStepTextDetailsHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..2aafa0c3b8 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedStepTextDetailsHyperskillAnalyticEvent.kt @@ -0,0 +1,31 @@ +package org.hyperskill.app.step_quiz.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the button that toggles the visibility of the step text description analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "main", + * "target": "step_text_description" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class StepQuizClickedStepTextDetailsHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.MAIN, + HyperskillAnalyticTarget.STEP_TEXT_DESCRIPTION +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..dfdd308786 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent.kt @@ -0,0 +1,31 @@ +package org.hyperskill.app.step_quiz.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the button that toggles the visibility of the input-output info for the code problem + * in the full screen code editor analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "full_screen_code_editor", + * "target": "input_output_info" + * } + * ``` + * @see HyperskillAnalyticEvent + */ +class StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.FULL_SCREEN_CODE_EDITOR, + HyperskillAnalyticTarget.INPUT_OUTPUT_INFO +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..9fcff7dd83 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent.kt @@ -0,0 +1,32 @@ +package org.hyperskill.app.step_quiz.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the button that toggles the visibility of the step text description analytic event in the + * full screen code editor. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "full_screen_code_editor", + * "target": "step_text_description" + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.FULL_SCREEN_CODE_EDITOR, + HyperskillAnalyticTarget.STEP_TEXT_DESCRIPTION +) \ 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 2dc93d192d..fb8ad33c8f 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 @@ -101,9 +101,16 @@ interface StepQuizFeature { * Analytic */ object ClickedCodeDetailsEventMessage : Message + object FullScreenCodeEditorClickedCodeDetailsEventMessage : Message + + object ClickedStepTextDetailsEventMessage : Message + object FullScreenCodeEditorClickedStepTextDetailsEventMessage : Message + object ClickedRetryEventMessage : Message + object ProblemsLimitReachedModalShownEventMessage : Message object ProblemsLimitReachedModalHiddenEventMessage : Message + object ParsonsProblemOnboardingModalHiddenEventMessage : Message } 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 919a8e848f..3e81e30c42 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 @@ -12,7 +12,10 @@ import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedCodeDetailsHy import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedRetryHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedRunHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedSendHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedStepTextDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbarItemHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.model.submissions.Reply import org.hyperskill.app.step_quiz.domain.model.submissions.Submission import org.hyperskill.app.step_quiz.domain.model.submissions.SubmissionStatus @@ -206,6 +209,34 @@ class StepQuizReducer( } else { null } + is Message.FullScreenCodeEditorClickedCodeDetailsEventMessage -> { + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val event = StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent( + stepRoute.analyticRoute + ) + state to setOf(Action.LogAnalyticEvent(event)) + } else { + null + } + } + is Message.ClickedStepTextDetailsEventMessage -> { + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val event = StepQuizClickedStepTextDetailsHyperskillAnalyticEvent(stepRoute.analyticRoute) + state to setOf(Action.LogAnalyticEvent(event)) + } else { + null + } + } + is Message.FullScreenCodeEditorClickedStepTextDetailsEventMessage -> { + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val event = StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent( + stepRoute.analyticRoute + ) + state to setOf(Action.LogAnalyticEvent(event)) + } else { + null + } + } is Message.TheoryToolbarItemClicked -> handleTheoryToolbarItemClicked(state) is Message.ClickedRetryEventMessage -> From 1f899c7ec3dd2c088c81179a4e60af5eaf5ba959 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 18:11:07 +0400 Subject: [PATCH 10/16] Log analytic event on click open full screen code editor --- .../StepQuizCode/StepQuizCodeViewModel.swift | 12 ++++++-- .../StepQuizCode/Views/StepQuizCodeView.swift | 4 +-- .../StepQuizPyCharm/StepQuizPyCharmView.swift | 4 +-- .../StepQuizSQL/Views/StepQuizSQLView.swift | 4 +-- .../hyperskill/HyperskillAnalyticPart.kt | 3 +- .../hyperskill/HyperskillAnalyticTarget.kt | 3 +- ...ScreenCodeEditorHyperskillAnalyticEvent.kt | 30 +++++++++++++++++++ .../step_quiz/presentation/StepQuizFeature.kt | 2 ++ .../step_quiz/presentation/StepQuizReducer.kt | 9 ++++++ 9 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent.kt diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift index cb72f518e9..cd469f9d80 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift @@ -44,8 +44,12 @@ class StepQuizCodeViewModel: ObservableObject { provideModuleInputCallback(self) } - func logClickedCodeDetailsEvent() { - moduleOutput?.handleChildQuizAnalyticEventMessage(StepQuizFeatureMessageClickedCodeDetailsEventMessage()) + func doFullScreenCodeEditorPresentation() { + navigationState.presentingFullScreen = true + + moduleOutput?.handleChildQuizAnalyticEventMessage( + StepQuizFeatureMessageClickedOpenFullScreenCodeEditorEventMessage() + ) } func handleCodeDidChange(_ newCode: String?) { @@ -55,6 +59,10 @@ class StepQuizCodeViewModel: ObservableObject { self.syncReply(code: newCode) } } + + func logClickedCodeDetailsEvent() { + moduleOutput?.handleChildQuizAnalyticEventMessage(StepQuizFeatureMessageClickedCodeDetailsEventMessage()) + } } // MARK: - StepQuizCodeViewModel: StepQuizChildQuizInputProtocol - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index 15977fe420..b778406529 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -21,9 +21,7 @@ struct StepQuizCodeView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: { - viewModel.navigationState.presentingFullScreen = true - } + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift index e7284f72cb..cb0b8fab7d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift @@ -16,9 +16,7 @@ struct StepQuizPyCharmView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: { - viewModel.navigationState.presentingFullScreen = true - } + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift index 9f6002e45b..2d1b783d6f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift @@ -16,9 +16,7 @@ struct StepQuizSQLView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: { - viewModel.navigationState.presentingFullScreen = true - } + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt index dc8cddf4d9..7f98e48db5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt @@ -33,5 +33,6 @@ enum class HyperskillAnalyticPart(val partName: String) { STAGE_COMPLETED_MODAL("stage_completed_modal"), PROJECT_COMPLETED_MODAL("project_completed_modal"), NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget"), - FULL_SCREEN_CODE_EDITOR("full_screen_code_editor") + FULL_SCREEN_CODE_EDITOR("full_screen_code_editor"), + CODE_EDITOR("code_editor") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index 7c8bf09fca..ace6048556 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -92,5 +92,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) { BADGE_MODAL("badge_modal"), EARNED_BADGE_MODAL("earned_badge_modal"), ALLOW_NOTIFICATIONS("allow_notifications"), - REMIND_ME_LATER("remind_me_later") + REMIND_ME_LATER("remind_me_later"), + FULL_SCREEN_CODE_EDITOR("full_screen_code_editor") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..e451bf5b12 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent.kt @@ -0,0 +1,30 @@ +package org.hyperskill.app.step_quiz.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the button that triggers navigation to the full screen code editor analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "code_editor", + * "target": "full_screen_code_editor" + * } + * ``` + * @see HyperskillAnalyticEvent + */ +class StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.CODE_EDITOR, + HyperskillAnalyticTarget.FULL_SCREEN_CODE_EDITOR +) \ 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 fb8ad33c8f..3fb18c9ec0 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 @@ -106,6 +106,8 @@ interface StepQuizFeature { object ClickedStepTextDetailsEventMessage : Message object FullScreenCodeEditorClickedStepTextDetailsEventMessage : Message + object ClickedOpenFullScreenCodeEditorEventMessage : Message + object ClickedRetryEventMessage : Message object ProblemsLimitReachedModalShownEventMessage : Message 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 3e81e30c42..7e834831f8 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 @@ -9,6 +9,7 @@ import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalCli import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalHiddenHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.ProblemsLimitReachedModalShownHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedCodeDetailsHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedRetryHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedRunHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedSendHyperskillAnalyticEvent @@ -237,6 +238,14 @@ class StepQuizReducer( null } } + is Message.ClickedOpenFullScreenCodeEditorEventMessage -> { + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val event = StepQuizClickedOpenFullScreenCodeEditorHyperskillAnalyticEvent(stepRoute.analyticRoute) + state to setOf(Action.LogAnalyticEvent(event)) + } else { + null + } + } is Message.TheoryToolbarItemClicked -> handleTheoryToolbarItemClicked(state) is Message.ClickedRetryEventMessage -> From d9878da0feb82707311ae34090df64e2fe240fdd Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 27 Sep 2023 18:48:29 +0400 Subject: [PATCH 11/16] Ignore bottom safe area in full screen code editor --- .../StepQuizCodeFullScreenAssembly.swift | 12 ++++++++++++ .../Views/StepQuizCodeFullScreenView.swift | 1 + 2 files changed, 13 insertions(+) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift index d03f13675f..f7c4575abf 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenAssembly.swift @@ -53,6 +53,18 @@ extension StepQuizCodeFullScreenAssembly { code: "fun main() {\n // put your code here\n}", codeTemplate: "fun main() {\n // put your code here\n}", samples: [ + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ), + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ), .init( inputTitle: "Sample Input 1", inputValue: "3\n3\n3", diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift index d0ae09defd..220e98d4e8 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift @@ -65,6 +65,7 @@ struct StepQuizCodeFullScreenView: View { .navigationBarTitleDisplayMode(.inline) .navigationTitle(navigationTitle) .toolbar(content: buildToolbarContent) + .edgesIgnoringSafeArea(.bottom) } .navigationViewStyle(StackNavigationViewStyle()) .onAppear { From d1988f6910dcf2c4ee310c6e103c464ec082c689 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 28 Sep 2023 13:22:43 +0400 Subject: [PATCH 12/16] Update code editor min height --- .../Sources/Models/Constants/Images/Images.swift | 1 - .../StepQuizCode/Views/StepQuizCodeEditorView.swift | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift index 1c2b57d3ab..4246058cb2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Images/Images.swift @@ -59,7 +59,6 @@ enum Images { static let discussions = "step-quiz-discussions" static let info = "step-quiz-info" static let lightning = "step-quiz-lightning" - static let expand = "step-quiz-code-editor-expand" enum Hints { static let helpfulReaction = "step_quiz_hints_helpful_reaction" diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift index 83ef803910..f9913a7665 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift @@ -2,8 +2,9 @@ import SwiftUI extension StepQuizCodeEditorView { struct Appearance { + static let codeEditorMinHeight: CGFloat = 80 + let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset) - let codeEditorMinHeight: CGFloat = 300 } } @@ -19,7 +20,7 @@ struct StepQuizCodeEditorView: View { @Environment(\.isEnabled) private var isEnabled - @State private var height: CGFloat = 300 + @State private var height: CGFloat = Self.Appearance.codeEditorMinHeight var body: some View { VStack(spacing: 0) { @@ -41,7 +42,7 @@ struct StepQuizCodeEditorView: View { Button( action: onExpandButtonTap, label: { - Image(Images.StepQuiz.expand) + Image(.stepQuizCodeEditorExpand) .renderingMode(.template) .font(.headline) .foregroundColor(.secondaryText) @@ -63,7 +64,7 @@ struct StepQuizCodeEditorView: View { isEditable: true, textInsets: appearance.codeEditorInsets.uiEdgeInsets, onDidChangeHeight: { newHeight in - let constrainMinimumHeight = max(newHeight, appearance.codeEditorMinHeight) + let constrainMinimumHeight = max(newHeight, Self.Appearance.codeEditorMinHeight) guard constrainMinimumHeight != height else { return } From 4405c08c082a9f4be2d2133f189a8bc81baf4bf4 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 28 Sep 2023 13:37:44 +0400 Subject: [PATCH 13/16] Use #Preview macro --- .../StepText/ExpandableStepTextView.swift | 16 +++--- .../Samples/StepQuizCodeSamplesView.swift | 39 ++++++------- .../Details/StepQuizCodeDetailsView.swift | 57 +++++++++---------- .../Views/StepQuizCodeEditorView.swift | 26 ++++++--- .../StepQuizCode/Views/StepQuizCodeView.swift | 24 ++++---- .../StepQuizCodeFullScreenDetailsView.swift | 30 +++++----- .../Views/StepQuizCodeFullScreenView.swift | 10 ++-- .../StepQuizPyCharm/StepQuizPyCharmView.swift | 13 ++--- .../StepQuizSQL/Views/StepQuizSQLView.swift | 24 ++++---- 9 files changed, 114 insertions(+), 125 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift index 04595d7c7f..c8c2532172 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/StepText/ExpandableStepTextView.swift @@ -55,15 +55,13 @@ struct ExpandableStepTextView: View { } } -struct ExpandableStepTextView_Previews: PreviewProvider { - static var previews: some View { - ExpandableStepTextView( - text: """ +#Preview { + ExpandableStepTextView( + text: """

Despite the fact that the syntax for different databases may differ, most of them have common standards.

""", - onExpandButtonTap: {} - ) - .padding() - .frame(height: 200) - } + onExpandButtonTap: {} + ) + .padding() + .frame(height: 200) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift index bfc321f75b..c86b57aef5 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/Samples/StepQuizCodeSamplesView.swift @@ -29,25 +29,22 @@ struct StepQuizCodeSamplesView: View { } } -struct StepQuizCodeSamplesView_Previews: PreviewProvider { - static var previews: some View { - StepQuizCodeSamplesView( - samples: [ - .init( - inputTitle: "Sample Input 1", - inputValue: "3\n3\n3", - outputTitle: "Sample Output 1", - outputValue: "true" - ), - .init( - inputTitle: "Sample Input 2", - inputValue: "3\n3\n3", - outputTitle: "Sample Output 2", - outputValue: "true" - ) - ] - ) - .previewLayout(.sizeThatFits) - .padding() - } +#Preview { + StepQuizCodeSamplesView( + samples: [ + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ), + .init( + inputTitle: "Sample Input 2", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 2", + outputValue: "true" + ) + ] + ) + .padding() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift index d0ee743655..a3fef89809 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/Details/StepQuizCodeDetailsView.swift @@ -51,36 +51,33 @@ struct StepQuizCodeDetailsView: View { } } -struct StepQuizCodeDetailsView_Previews: PreviewProvider { - static var previews: some View { - Group { - StepQuizCodeDetailsView( - samples: [ - .init( - inputTitle: "Sample Input 1", - inputValue: "3\n3\n3", - outputTitle: "Sample Output 1", - outputValue: "true" - ) - ], - isExpanded: false, - onExpandTapped: {} - ) +#Preview { + Group { + StepQuizCodeDetailsView( + samples: [ + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ) + ], + isExpanded: false, + onExpandTapped: {} + ) - StepQuizCodeDetailsView( - samples: [ - .init( - inputTitle: "Sample Input 1", - inputValue: "3\n3\n3", - outputTitle: "Sample Output 1", - outputValue: "true" - ) - ], - isExpanded: true, - onExpandTapped: {} - ) - } - .previewLayout(.sizeThatFits) - .padding() + StepQuizCodeDetailsView( + samples: [ + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ) + ], + isExpanded: true, + onExpandTapped: {} + ) } + .padding() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift index f9913a7665..b88f61a937 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift @@ -83,13 +83,21 @@ struct StepQuizCodeEditorView: View { } } -struct StepQuizCodeEditorView_Previews: PreviewProvider { - static var previews: some View { - StepQuizCodeEditorView( - code: .constant(CodeLanguageSamples.sample(for: .java)), - codeTemplate: nil, - language: .java, - onExpandButtonTap: {} - ) - } +#Preview("Light") { + StepQuizCodeEditorView( + code: .constant(CodeLanguageSamples.sample(for: .java)), + codeTemplate: nil, + language: .java, + onExpandButtonTap: {} + ) +} + +#Preview("Dark") { + StepQuizCodeEditorView( + code: .constant(CodeLanguageSamples.sample(for: .java)), + codeTemplate: nil, + language: .java, + onExpandButtonTap: {} + ) + .preferredColorScheme(.dark) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index b778406529..118fc2c5f6 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -38,20 +38,18 @@ struct StepQuizCodeView: View { } #if DEBUG -struct StepQuizCodeView_Previews: PreviewProvider { - static var previews: some View { - Group { - StepQuizCodeAssembly - .makePlaceholder() - .makeModule() +#Preview("Light") { + StepQuizCodeAssembly + .makePlaceholder() + .makeModule() + .padding() +} - StepQuizCodeAssembly - .makePlaceholder() - .makeModule() - .preferredColorScheme(.dark) - } - .previewLayout(.sizeThatFits) +#Preview("Dark") { + StepQuizCodeAssembly + .makePlaceholder() + .makeModule() .padding() - } + .preferredColorScheme(.dark) } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift index 756143bdcf..7a1cb25bb7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenDetailsView.swift @@ -39,22 +39,20 @@ struct StepQuizCodeFullScreenDetailsView: View { } } -struct StepQuizCodeFullScreenDetailsView_Previews: PreviewProvider { - static var previews: some View { - StepQuizCodeFullScreenDetailsView( - stepText: """ +#Preview { + StepQuizCodeFullScreenDetailsView( + stepText: """ Enter only the name of the found functional interface with/without the package. Don't write any generic parameters. """, - samples: [ - .init( - inputTitle: "Sample Input 1", - inputValue: "3\n3\n3", - outputTitle: "Sample Output 1", - outputValue: "true" - ) - ], - onExpandStepTextButtonTap: {}, - onExpandCodeDetailsButtonTap: {} - ) - } + samples: [ + .init( + inputTitle: "Sample Input 1", + inputValue: "3\n3\n3", + outputTitle: "Sample Output 1", + outputValue: "true" + ) + ], + onExpandStepTextButtonTap: {}, + onExpandCodeDetailsButtonTap: {} + ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift index 220e98d4e8..c085627a7d 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift @@ -105,11 +105,9 @@ struct StepQuizCodeFullScreenView: View { } #if DEBUG -struct StepQuizCodeFullScreenView_Previews: PreviewProvider { - static var previews: some View { - StepQuizCodeFullScreenAssembly - .makePlaceholder() - .makeModule() - } +#Preview { + StepQuizCodeFullScreenAssembly + .makePlaceholder() + .makeModule() } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift index cb0b8fab7d..7cc5ec4d61 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift @@ -33,13 +33,10 @@ struct StepQuizPyCharmView: View { } #if DEBUG -struct StepQuizPyCharmView_Previews: PreviewProvider { - static var previews: some View { - StepQuizPyCharmAssembly - .makePlaceholder() - .makeModule() - .previewLayout(.sizeThatFits) - .padding() - } +#Preview { + StepQuizPyCharmAssembly + .makePlaceholder() + .makeModule() + .padding() } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift index 2d1b783d6f..be80f02d16 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift @@ -34,20 +34,18 @@ struct StepQuizSQLView: View { } #if DEBUG -struct StepQuizSQLView_Previews: PreviewProvider { - static var previews: some View { - Group { - StepQuizSQLAssembly - .makePlaceholder() - .makeModule() +#Preview("Light") { + StepQuizSQLAssembly + .makePlaceholder() + .makeModule() + .padding() +} - StepQuizSQLAssembly - .makePlaceholder() - .makeModule() - .preferredColorScheme(.dark) - } - .previewLayout(.sizeThatFits) +#Preview("Dark") { + StepQuizSQLAssembly + .makePlaceholder() + .makeModule() + .preferredColorScheme(.dark) .padding() - } } #endif From 198e83868889bcb3ab7b9824dd429f830537451e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 29 Sep 2023 15:57:18 +0400 Subject: [PATCH 14/16] Update plugin-ktlint --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ea6978264..0ca286d169 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ plugin-kotlinSerialization = { module = "org.jetbrains.kotlin:kotlin-serializati plugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } plugin-dokkaBase = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" } plugin-android = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } -plugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version = "10.2.0" } +plugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version = "11.1.0" } plugin-gradleVersionUpdates = { module = "com.github.ben-manes:gradle-versions-plugin", version = "0.47.0" } plugin-buildKonfig = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version = "0.13.3" } plugin-mokoResources = { module = "dev.icerock.moko:resources-generator", version.ref = "mokoResources" } From 48aa859ceaf60c1e484d7d174242f269b3db6129 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 29 Sep 2023 18:02:47 +0400 Subject: [PATCH 15/16] Log analytic event on click in input accessory view --- .../CodeEditor/View/SwiftUI/CodeEditor.swift | 18 +++++++ .../UIKit/CodeEditorView/CodeEditorView.swift | 12 ++++- .../CodeEditorViewDelegate.swift | 10 ++++ .../StepQuizCode/StepQuizCodeViewModel.swift | 10 ++++ .../Views/StepQuizCodeEditorView.swift | 11 ++-- .../StepQuizCode/Views/StepQuizCodeView.swift | 3 +- ...StepQuizCodeFullScreenOutputProtocol.swift | 1 + .../StepQuizCodeFullScreenViewModel.swift | 4 ++ .../StepQuizCodeFullScreenCodeView.swift | 30 ++++++----- .../Views/StepQuizCodeFullScreenView.swift | 3 +- .../StepQuizPyCharm/StepQuizPyCharmView.swift | 3 +- .../StepQuizSQL/Views/StepQuizSQLView.swift | 3 +- .../hyperskill/HyperskillAnalyticTarget.kt | 3 +- ...tAccessoryButtonHyperskillAnalyticEvent.kt | 52 +++++++++++++++++++ .../step_quiz/presentation/StepQuizFeature.kt | 2 + .../step_quiz/presentation/StepQuizReducer.kt | 12 +++++ 16 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent.kt diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift index 2541104a3b..046ecc5e1b 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/SwiftUI/CodeEditor.swift @@ -30,6 +30,8 @@ struct CodeEditor: UIViewRepresentable { var onDidChangeHeight: ((CGFloat) -> Void)? + var onDidTapInputAccessoryButton: ((String) -> Void)? + // MARK: UIViewRepresentable static func dismantleUIView(_ uiView: CodeEditorView, coordinator: Coordinator) { @@ -39,6 +41,7 @@ struct CodeEditor: UIViewRepresentable { coordinator.onDidBeginEditing = nil coordinator.onDidEndEditing = nil coordinator.onDidChangeHeight = nil + coordinator.onDidTapInputAccessoryButton = nil coordinator.suggestionsPresentationContextProvider = nil } @@ -93,6 +96,7 @@ struct CodeEditor: UIViewRepresentable { onDidEndEditing?() } context.coordinator.onDidChangeHeight = onDidChangeHeight + context.coordinator.onDidTapInputAccessoryButton = onDidTapInputAccessoryButton } } @@ -110,6 +114,8 @@ extension CodeEditor { var onDidChangeHeight: ((CGFloat) -> Void)? + var onDidTapInputAccessoryButton: ((String) -> Void)? + init(suggestionsPresentationContextProvider: CodeEditorSuggestionsPresentationContextProviding?) { self.suggestionsPresentationContextProvider = suggestionsPresentationContextProvider } @@ -137,6 +143,18 @@ extension CodeEditor { func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat) { onDidChangeHeight?(height) } + + func codeEditorViewDidTapTabInputAccessoryButton(_ codeEditorView: CodeEditorView) { + onDidTapInputAccessoryButton?("tab") + } + + func codeEditorViewDidTapHideKeyboardInputAccessoryButton(_ codeEditorView: CodeEditorView) { + onDidTapInputAccessoryButton?("hide") + } + + func codeEditorView(_ codeEditorView: CodeEditorView, didTapInputAccessoryButton symbol: String) { + onDidTapInputAccessoryButton?(symbol) + } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift index bcfa8c4928..ee1026deb4 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorView.swift @@ -139,6 +139,8 @@ final class CodeEditorView: UIView { return } + strongSelf.delegate?.codeEditorViewDidTapTabInputAccessoryButton(strongSelf) + strongSelf.codePlaygroundManager.insertAtCurrentPosition( symbols: String(repeating: " ", count: strongSelf.tabSize), textView: strongSelf.codeTextView @@ -149,6 +151,8 @@ final class CodeEditorView: UIView { return } + strongSelf.delegate?.codeEditorView(strongSelf, didTapInputAccessoryButton: symbols) + strongSelf.codePlaygroundManager.insertAtCurrentPosition( symbols: symbols, textView: strongSelf.codeTextView @@ -156,7 +160,13 @@ final class CodeEditorView: UIView { strongSelf.analyzeCodeAndComplete() }, hideKeyboardAction: { [weak self] in - self?.codeTextView.resignFirstResponder() + guard let strongSelf = self else { + return + } + + strongSelf.delegate?.codeEditorViewDidTapHideKeyboardInputAccessoryButton(strongSelf) + + strongSelf.codeTextView.resignFirstResponder() }, pasteConfigurationSupporting: codeTextView ) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift index 36a317108a..4d25c1dd49 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Frameworks/CodeEditor/View/UIKit/CodeEditorView/CodeEditorViewDelegate.swift @@ -7,6 +7,10 @@ protocol CodeEditorViewDelegate: AnyObject { func codeEditorViewDidEndEditing(_ codeEditorView: CodeEditorView) func codeEditorViewDidRequestSuggestionPresentationController(_ codeEditorView: CodeEditorView) -> UIViewController? func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat) + + func codeEditorViewDidTapTabInputAccessoryButton(_ codeEditorView: CodeEditorView) + func codeEditorViewDidTapHideKeyboardInputAccessoryButton(_ codeEditorView: CodeEditorView) + func codeEditorView(_ codeEditorView: CodeEditorView, didTapInputAccessoryButton symbol: String) } extension CodeEditorViewDelegate { @@ -23,4 +27,10 @@ extension CodeEditorViewDelegate { ) -> UIViewController? { nil } + + func codeEditorViewDidTapTabInputAccessoryButton(_ codeEditorView: CodeEditorView) {} + + func codeEditorViewDidTapHideKeyboardInputAccessoryButton(_ codeEditorView: CodeEditorView) {} + + func codeEditorView(_ codeEditorView: CodeEditorView, didTapInputAccessoryButton symbol: String) {} } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift index cd469f9d80..560f74facc 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift @@ -63,6 +63,12 @@ class StepQuizCodeViewModel: ObservableObject { func logClickedCodeDetailsEvent() { moduleOutput?.handleChildQuizAnalyticEventMessage(StepQuizFeatureMessageClickedCodeDetailsEventMessage()) } + + func logClickedInputAccessoryButton(symbol: String) { + moduleOutput?.handleChildQuizAnalyticEventMessage( + StepQuizFeatureMessageCodeEditorClickedInputAccessoryButtonEvent(symbol: symbol) + ) + } } // MARK: - StepQuizCodeViewModel: StepQuizChildQuizInputProtocol - @@ -130,6 +136,10 @@ extension StepQuizCodeViewModel: StepQuizCodeFullScreenOutputProtocol { ) } + func handleStepQuizCodeFullScreenTappedInputAccessoryButton(symbol: String) { + logClickedInputAccessoryButton(symbol: symbol) + } + @objc func syncReply(code: String?) { let reply = Reply(language: viewData.languageStringValue, code: code) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift index b88f61a937..7e8acdfc16 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeEditorView.swift @@ -18,6 +18,8 @@ struct StepQuizCodeEditorView: View { let onExpandButtonTap: () -> Void + let onInputAccessoryButtonTap: (String) -> Void + @Environment(\.isEnabled) private var isEnabled @State private var height: CGFloat = Self.Appearance.codeEditorMinHeight @@ -73,7 +75,8 @@ struct StepQuizCodeEditorView: View { height = constrainMinimumHeight KeyboardManager.reloadLayoutIfNeeded() } - } + }, + onDidTapInputAccessoryButton: onInputAccessoryButtonTap ) .frame(height: height) .frame(maxWidth: .infinity) @@ -88,7 +91,8 @@ struct StepQuizCodeEditorView: View { code: .constant(CodeLanguageSamples.sample(for: .java)), codeTemplate: nil, language: .java, - onExpandButtonTap: {} + onExpandButtonTap: {}, + onInputAccessoryButtonTap: { _ in } ) } @@ -97,7 +101,8 @@ struct StepQuizCodeEditorView: View { code: .constant(CodeLanguageSamples.sample(for: .java)), codeTemplate: nil, language: .java, - onExpandButtonTap: {} + onExpandButtonTap: {}, + onInputAccessoryButtonTap: { _ in } ) .preferredColorScheme(.dark) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift index 118fc2c5f6..fdde197200 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/Views/StepQuizCodeView.swift @@ -21,7 +21,8 @@ struct StepQuizCodeView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation, + onInputAccessoryButtonTap: viewModel.logClickedInputAccessoryButton(symbol:) ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift index 5896edd8bc..013705ac7f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/InputOutput/StepQuizCodeFullScreenOutputProtocol.swift @@ -7,4 +7,5 @@ protocol StepQuizCodeFullScreenOutputProtocol: AnyObject { // Analytic func handleStepQuizCodeFullScreenToggledStepTextDetails() func handleStepQuizCodeFullScreenToggledCodeDetails() + func handleStepQuizCodeFullScreenTappedInputAccessoryButton(symbol: String) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift index 898f085946..2e46c44065 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/StepQuizCodeFullScreenViewModel.swift @@ -48,6 +48,10 @@ final class StepQuizCodeFullScreenViewModel: ObservableObject { func logClickedCodeDetailsEvent() { moduleOutput?.handleStepQuizCodeFullScreenToggledCodeDetails() } + + func logClickedInputAccessoryButton(symbol: String) { + moduleOutput?.handleStepQuizCodeFullScreenTappedInputAccessoryButton(symbol: symbol) + } } // MARK: - StepQuizCodeFullScreenViewModel: StepQuizCodeFullScreenInputProtocol - diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenCodeView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenCodeView.swift index 9222325cca..f4d5486164 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenCodeView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenCodeView.swift @@ -29,6 +29,8 @@ struct StepQuizCodeFullScreenCodeView: View { let onTapRetry: () -> Void let onTapRunCode: () -> Void + let onDidTapInputAccessoryButton: (String) -> Void + var body: some View { ZStack(alignment: .bottom) { CodeEditor( @@ -37,7 +39,8 @@ struct StepQuizCodeFullScreenCodeView: View { language: language, textInsets: appearance.codeEditorTextInsets, onDidBeginEditing: onDidBeginEditingCode, - onDidEndEditing: onDidEndEditingCode + onDidEndEditing: onDidEndEditingCode, + onDidTapInputAccessoryButton: onDidTapInputAccessoryButton ) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -57,17 +60,16 @@ struct StepQuizCodeFullScreenCodeView: View { } } -struct StepQuizCodeFullScreenCodeView_Previews: PreviewProvider { - static var previews: some View { - StepQuizCodeFullScreenCodeView( - code: .constant("fun main() {\n // put your code here\n}"), - codeTemplate: "fun main() {\n // put your code here\n}", - language: .kotlin, - isActionButtonsVisible: true, - onDidBeginEditingCode: {}, - onDidEndEditingCode: {}, - onTapRetry: {}, - onTapRunCode: {} - ) - } +#Preview { + StepQuizCodeFullScreenCodeView( + code: .constant("fun main() {\n // put your code here\n}"), + codeTemplate: "fun main() {\n // put your code here\n}", + language: .kotlin, + isActionButtonsVisible: true, + onDidBeginEditingCode: {}, + onDidEndEditingCode: {}, + onTapRetry: {}, + onTapRunCode: {}, + onDidTapInputAccessoryButton: { _ in } + ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift index c085627a7d..c57ec500f2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeFullScreen/Views/StepQuizCodeFullScreenView.swift @@ -55,7 +55,8 @@ struct StepQuizCodeFullScreenView: View { } }, onTapRetry: viewModel.doRetry, - onTapRunCode: viewModel.doRunCode + onTapRunCode: viewModel.doRunCode, + onDidTapInputAccessoryButton: viewModel.logClickedInputAccessoryButton(symbol:) ) .onChange(of: viewModel.codeQuizViewData.code, perform: viewModel.doCodeUpdate(code:)) .tag(StepQuizCodeFullScreenTab.code) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift index 7cc5ec4d61..c2ef27be31 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizPyCharm/StepQuizPyCharmView.swift @@ -16,7 +16,8 @@ struct StepQuizPyCharmView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation, + onInputAccessoryButtonTap: viewModel.logClickedInputAccessoryButton(symbol:) ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift index be80f02d16..867c9930ce 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizSQL/Views/StepQuizSQLView.swift @@ -16,7 +16,8 @@ struct StepQuizSQLView: View { ), codeTemplate: viewData.codeTemplate, language: viewData.language, - onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation + onExpandButtonTap: viewModel.doFullScreenCodeEditorPresentation, + onInputAccessoryButtonTap: viewModel.logClickedInputAccessoryButton(symbol:) ) .padding(.horizontal, -LayoutInsets.defaultInset) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index ace6048556..679ec46421 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -93,5 +93,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) { EARNED_BADGE_MODAL("earned_badge_modal"), ALLOW_NOTIFICATIONS("allow_notifications"), REMIND_ME_LATER("remind_me_later"), - FULL_SCREEN_CODE_EDITOR("full_screen_code_editor") + FULL_SCREEN_CODE_EDITOR("full_screen_code_editor"), + CODE_INPUT_ACCESSORY_BUTTON("code_input_accessory_button") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent.kt new file mode 100644 index 0000000000..63b620b0de --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/domain/analytic/StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent.kt @@ -0,0 +1,52 @@ +package org.hyperskill.app.step_quiz.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget + +/** + * Represents click on the code input accessory button. + * + * The custom accessory view is displayed when the text view becomes the first responder (over keyboard). + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "code_editor", + * "target": "code_input_accessory_button", + * "context": + * { + * "symbol": "tab" + * } + * } + * ``` + * + * @property symbol Tab, hide keyboard and other actions are handled by the accessory view. + * + * @see HyperskillAnalyticEvent + */ +class StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute, + val symbol: String +) : HyperskillAnalyticEvent( + route, + HyperskillAnalyticAction.CLICK, + HyperskillAnalyticPart.CODE_EDITOR, + HyperskillAnalyticTarget.CODE_INPUT_ACCESSORY_BUTTON +) { + companion object { + private const val SYMBOL = "symbol" + } + + override val params: Map + get() = super.params + + mapOf( + PARAM_CONTEXT to mapOf( + SYMBOL to symbol + ) + ) +} \ 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 3fb18c9ec0..6659d39b02 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 @@ -108,6 +108,8 @@ interface StepQuizFeature { object ClickedOpenFullScreenCodeEditorEventMessage : Message + data class CodeEditorClickedInputAccessoryButtonEventMessage(val symbol: String) : Message + object ClickedRetryEventMessage : Message object ProblemsLimitReachedModalShownEventMessage : Message 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 7e834831f8..50a18b3f91 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 @@ -15,6 +15,7 @@ import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedRunHyperskill import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedSendHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedStepTextDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizClickedTheoryToolbarItemHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz.domain.analytic.StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedCodeDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.analytic.StepQuizFullScreenCodeEditorClickedStepTextDetailsHyperskillAnalyticEvent import org.hyperskill.app.step_quiz.domain.model.submissions.Reply @@ -246,6 +247,17 @@ class StepQuizReducer( null } } + is Message.CodeEditorClickedInputAccessoryButtonEventMessage -> { + if (state.stepQuizState is StepQuizState.AttemptLoaded) { + val event = StepQuizCodeEditorClickedInputAccessoryButtonHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + symbol = message.symbol + ) + state to setOf(Action.LogAnalyticEvent(event)) + } else { + null + } + } is Message.TheoryToolbarItemClicked -> handleTheoryToolbarItemClicked(state) is Message.ClickedRetryEventMessage -> From 650620282cbdd2b81c335d430b2a793a22aa015f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 29 Sep 2023 18:24:36 +0400 Subject: [PATCH 16/16] Fix iOS build --- .../StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift index 560f74facc..cb4434b7cd 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCode/StepQuizCodeViewModel.swift @@ -66,7 +66,7 @@ class StepQuizCodeViewModel: ObservableObject { func logClickedInputAccessoryButton(symbol: String) { moduleOutput?.handleChildQuizAnalyticEventMessage( - StepQuizFeatureMessageCodeEditorClickedInputAccessoryButtonEvent(symbol: symbol) + StepQuizFeatureMessageCodeEditorClickedInputAccessoryButtonEventMessage(symbol: symbol) ) } }