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 0000000000..698445bdec Binary files /dev/null and b/iosHyperskillApp/iosHyperskillApp/Assets.xcassets/StepQuiz/step-quiz-code-editor-expand.imageset/step-quiz-code-editor-expand.pdf differ 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