Skip to content

Commit

Permalink
Log analytic event on click in input accessory view
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-magda committed Sep 29, 2023
1 parent 198e838 commit 48aa859
Show file tree
Hide file tree
Showing 16 changed files with 154 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct CodeEditor: UIViewRepresentable {

var onDidChangeHeight: ((CGFloat) -> Void)?

var onDidTapInputAccessoryButton: ((String) -> Void)?

// MARK: UIViewRepresentable

static func dismantleUIView(_ uiView: CodeEditorView, coordinator: Coordinator) {
Expand All @@ -39,6 +41,7 @@ struct CodeEditor: UIViewRepresentable {
coordinator.onDidBeginEditing = nil
coordinator.onDidEndEditing = nil
coordinator.onDidChangeHeight = nil
coordinator.onDidTapInputAccessoryButton = nil
coordinator.suggestionsPresentationContextProvider = nil
}

Expand Down Expand Up @@ -93,6 +96,7 @@ struct CodeEditor: UIViewRepresentable {
onDidEndEditing?()
}
context.coordinator.onDidChangeHeight = onDidChangeHeight
context.coordinator.onDidTapInputAccessoryButton = onDidTapInputAccessoryButton
}
}

Expand All @@ -110,6 +114,8 @@ extension CodeEditor {

var onDidChangeHeight: ((CGFloat) -> Void)?

var onDidTapInputAccessoryButton: ((String) -> Void)?

init(suggestionsPresentationContextProvider: CodeEditorSuggestionsPresentationContextProviding?) {
self.suggestionsPresentationContextProvider = suggestionsPresentationContextProvider
}
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -149,14 +151,22 @@ final class CodeEditorView: UIView {
return
}

strongSelf.delegate?.codeEditorView(strongSelf, didTapInputAccessoryButton: symbols)

strongSelf.codePlaygroundManager.insertAtCurrentPosition(
symbols: symbols,
textView: strongSelf.codeTextView
)
strongSelf.analyzeCodeAndComplete()
},
hideKeyboardAction: { [weak self] in
self?.codeTextView.resignFirstResponder()
guard let strongSelf = self else {
return
}

strongSelf.delegate?.codeEditorViewDidTapHideKeyboardInputAccessoryButton(strongSelf)

strongSelf.codeTextView.resignFirstResponder()
},
pasteConfigurationSupporting: codeTextView
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,4 +27,10 @@ extension CodeEditorViewDelegate {
) -> UIViewController? {
nil
}

func codeEditorViewDidTapTabInputAccessoryButton(_ codeEditorView: CodeEditorView) {}

func codeEditorViewDidTapHideKeyboardInputAccessoryButton(_ codeEditorView: CodeEditorView) {}

func codeEditorView(_ codeEditorView: CodeEditorView, didTapInputAccessoryButton symbol: String) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class StepQuizCodeViewModel: ObservableObject {
func logClickedCodeDetailsEvent() {
moduleOutput?.handleChildQuizAnalyticEventMessage(StepQuizFeatureMessageClickedCodeDetailsEventMessage())
}

func logClickedInputAccessoryButton(symbol: String) {
moduleOutput?.handleChildQuizAnalyticEventMessage(
StepQuizFeatureMessageCodeEditorClickedInputAccessoryButtonEvent(symbol: symbol)
)
}
}

// MARK: - StepQuizCodeViewModel: StepQuizChildQuizInputProtocol -
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,7 +75,8 @@ struct StepQuizCodeEditorView: View {
height = constrainMinimumHeight
KeyboardManager.reloadLayoutIfNeeded()
}
}
},
onDidTapInputAccessoryButton: onInputAccessoryButtonTap
)
.frame(height: height)
.frame(maxWidth: .infinity)
Expand All @@ -88,7 +91,8 @@ struct StepQuizCodeEditorView: View {
code: .constant(CodeLanguageSamples.sample(for: .java)),
codeTemplate: nil,
language: .java,
onExpandButtonTap: {}
onExpandButtonTap: {},
onInputAccessoryButtonTap: { _ in }
)
}

Expand All @@ -97,7 +101,8 @@ struct StepQuizCodeEditorView: View {
code: .constant(CodeLanguageSamples.sample(for: .java)),
codeTemplate: nil,
language: .java,
onExpandButtonTap: {}
onExpandButtonTap: {},
onInputAccessoryButtonTap: { _ in }
)
.preferredColorScheme(.dark)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ protocol StepQuizCodeFullScreenOutputProtocol: AnyObject {
// Analytic
func handleStepQuizCodeFullScreenToggledStepTextDetails()
func handleStepQuizCodeFullScreenToggledCodeDetails()
func handleStepQuizCodeFullScreenTappedInputAccessoryButton(symbol: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ final class StepQuizCodeFullScreenViewModel: ObservableObject {
func logClickedCodeDetailsEvent() {
moduleOutput?.handleStepQuizCodeFullScreenToggledCodeDetails()
}

func logClickedInputAccessoryButton(symbol: String) {
moduleOutput?.handleStepQuizCodeFullScreenTappedInputAccessoryButton(symbol: symbol)
}
}

// MARK: - StepQuizCodeFullScreenViewModel: StepQuizCodeFullScreenInputProtocol -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)

Expand All @@ -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 }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Original file line number Diff line number Diff line change
@@ -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<String, Any>
get() = super.params +
mapOf(
PARAM_CONTEXT to mapOf(
SYMBOL to symbol
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ interface StepQuizFeature {

object ClickedOpenFullScreenCodeEditorEventMessage : Message

data class CodeEditorClickedInputAccessoryButtonEventMessage(val symbol: String) : Message

object ClickedRetryEventMessage : Message

object ProblemsLimitReachedModalShownEventMessage : Message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ->
Expand Down

0 comments on commit 48aa859

Please sign in to comment.