Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALTAPPS-950: Android make code editor an active input #692

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3e1e99c
Remove stepQuizDescription for code quizes
XanderZhu Sep 27, 2023
b136c31
Extract codeEditorKeyboardExtension logic from CodeStepQuizFullScreen…
XanderZhu Sep 29, 2023
bce05bd
Implement CodeEditorKeyboardExtension in CodeStepQuizFragment
XanderZhu Sep 29, 2023
cbe69c1
Merge branch 'develop' into feature/ALTAPPS-950/Android-make-code-edi…
XanderZhu Oct 2, 2023
e4296b4
Add codeEditor header
XanderZhu Oct 2, 2023
71ffa12
Remove limits view from step
XanderZhu Oct 2, 2023
69422b1
Update codeEditor height
XanderZhu Oct 2, 2023
92165f0
Hide views, when keyboard is shown
XanderZhu Oct 2, 2023
3182ebc
Apply toolbarHeight to code editor
XanderZhu Oct 2, 2023
16823bb
Make step practice description collapsible
XanderZhu Oct 3, 2023
7609fc5
Fix build
XanderZhu Oct 3, 2023
4d4f7e8
Update codeSample block
XanderZhu Oct 3, 2023
a5a0283
Update fullScreen code details header
XanderZhu Oct 3, 2023
4cce2d3
Refactor collapsibleBlock setup
XanderZhu Oct 3, 2023
0e4d79e
Update colors
XanderZhu Oct 3, 2023
a7b52fd
Use stepQuizResolver.shouldSyncReply to distinct redundant syncs
XanderZhu Oct 3, 2023
9112d85
Setup analytics
XanderZhu Oct 4, 2023
a734ad1
Fix ktlint
XanderZhu Oct 4, 2023
2f300fd
Fix Android build
XanderZhu Oct 4, 2023
54b88f2
Show language with version on in code editor
XanderZhu Oct 4, 2023
060ec7b
Revert distinct sync message
XanderZhu Oct 4, 2023
b39d2cc
Replace SqlStepQuizFragment with CodeStepQuizFragment
XanderZhu Oct 4, 2023
96b7b73
Fix ktlint
XanderZhu Oct 4, 2023
5b7ef55
Merge branch 'develop' into feature/ALTAPPS-950/Android-make-code-edi…
XanderZhu Oct 4, 2023
ee576d5
Disable fullScreenCodeEditor open when quiz is disabled
XanderZhu Oct 5, 2023
5c439ed
Add missed bottomMargin for fullscreen editor details
XanderZhu Oct 5, 2023
322bc45
Fix expandButton enabled
XanderZhu Oct 5, 2023
0078f2f
Merge branch 'develop' into feature/ALTAPPS-950/Android-make-code-edi…
XanderZhu Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.hyperskill.app.android.code.util

import android.content.Context
import android.view.View
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.hyperskill.app.android.R
import org.hyperskill.app.android.code.view.adapter.CodeToolbarAdapter
import org.hyperskill.app.android.code.view.widget.CodeEditorLayout
import org.hyperskill.app.android.view.base.ui.extension.setOnKeyboardOpenListener

object CodeEditorKeyboardExtensionUtil {

fun interface CodeEditorKeyboardListener {
fun onCodeEditorKeyboardStateChanged(
isKeyboardShown: Boolean,
toolbarHeight: Int
)
}

fun setupKeyboardExtension(
context: Context,
rootView: View,
recyclerView: RecyclerView,
codeLayout: CodeEditorLayout,
codeToolbarAdapter: CodeToolbarAdapter,
isToolbarEnabled: () -> Boolean = { true },
onToolbarSymbolClicked: ((String) -> Unit)? = null,
codeEditorKeyboardListener: CodeEditorKeyboardListener? = null
) {
codeToolbarAdapter.onSymbolClickListener = CodeToolbarAdapter.OnSymbolClickListener { symbol, offset ->
onToolbarSymbolClicked?.invoke(symbol)
applySymbolTo(codeLayout, symbol, offset)
}

codeLayout.codeToolbarAdapter = codeToolbarAdapter

setupRecycler(context, recyclerView, codeToolbarAdapter)

// Flag is necessary,
// because keyboard listener is constantly invoked
// (probably global layout listener reacts to view changes)
var keyboardShown = false

setOnKeyboardOpenListener(
rootView,
onKeyboardHidden = {
if (keyboardShown) {
recyclerView.isInvisible = true
codeLayout.isNestedScrollingEnabled = true
codeEditorKeyboardListener?.onCodeEditorKeyboardStateChanged(
isKeyboardShown = false,
toolbarHeight = 0
)
keyboardShown = false
}
},
onKeyboardShown = {
if (!keyboardShown) {
if (isToolbarEnabled()) {
recyclerView.isInvisible = false
}
codeLayout.isNestedScrollingEnabled = false
codeEditorKeyboardListener?.onCodeEditorKeyboardStateChanged(
isKeyboardShown = true,
toolbarHeight = recyclerView.height
)
keyboardShown = true
}
}
)
}

private fun setupRecycler(context: Context, recyclerView: RecyclerView, codeToolbarAdapter: CodeToolbarAdapter) {
with(recyclerView) {
adapter = codeToolbarAdapter
layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
setBackgroundResource(R.color.color_elevation_overlay_2dp)
clipToPadding = false
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
isMotionEventSplittingEnabled = false
isInvisible = true
}
}

private fun applySymbolTo(codeEditorLayout: CodeEditorLayout, symbol: String, offset: Int) {
codeEditorLayout.insertText(
mapToolbarSymbolToPrintable(
symbol,
codeEditorLayout.indentSize
),
offset
)
}

private fun mapToolbarSymbolToPrintable(symbol: String, indentSize: Int): String =
if (symbol.equals("tab", ignoreCase = true)) {
" ".repeat(indentSize)
} else {
symbol
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class CodeToolbarAdapter(private val context: Context) :
private const val ELEMENT_VIEW_TYPE = 0
}

interface OnSymbolClickListener {
fun onSymbolClick(symbol: String, offset: Int = 0)
fun interface OnSymbolClickListener {
fun onSymbolClick(symbol: String, offset: Int)
}

private val autocompletePrefixBackgroundSpan =
Expand Down Expand Up @@ -54,7 +54,7 @@ class CodeToolbarAdapter(private val context: Context) :
) {
onSymbolClickListener?.onSymbolClick("$word ", autocomplete.prefix.length)
} else {
onSymbolClickListener?.onSymbolClick(word)
onSymbolClickListener?.onSymbolClick(word, 0)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ import androidx.core.view.isVisible

class ProgressableWebViewClient(
private val progressView: View,
private val webView: View,
context: Context = progressView.context
) : ExternalLinkWebViewClient(context) {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
progressView.isVisible = true
webView.isVisible = false
view?.isVisible = false
}

override fun onPageFinished(view: WebView?, url: String?) {
progressView.isVisible = false
webView.isVisible = true
view?.isVisible = true
super.onPageFinished(view, url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,23 @@ constructor(
@IdRes
private val webViewId: Int

lateinit var textView: TextView
var textView: TextView? = null
private set

lateinit var webView: LatexWebView
var webView: LatexWebView? = null
private set

var attributes = TextAttributes.fromAttributeSet(context, attrs)
set(value) {
field = value
textView.setAttributes(value)
webView.attributes = value
textView?.setAttributes(value)
webView?.attributes = value
}

var latexData: LatexData? = null
set(value) {
textView.isVisible = value is LatexData.Text
webView.isVisible = value is LatexData.Web
textView?.isVisible = value is LatexData.Text
webView?.isVisible = value is LatexData.Web

if (field == value) return
field = value
Expand All @@ -74,20 +74,23 @@ constructor(

when (value) {
is LatexData.Text ->
textView.text = value.text
textView?.text = value.text

is LatexData.Web -> {
webView.text = latexWebViewMapper.mapLatexData(value, webView.attributes)

// TODO Switch to WebViewAssetLoader
/**
* Allow WebView to open file://
*/
webView.settings.allowFileAccess = true
/**
* Kotlin Playground downloads a file with Kotlin versions which generates a mistake, if allowUniversalAccessFromFileURLs is false
*/
webView.settings.allowUniversalAccessFromFileURLs = value.settings.allowUniversalAccessFromFileURLs
webView?.apply {
text = latexWebViewMapper.mapLatexData(value, attributes)

// TODO Switch to WebViewAssetLoader
/**
* Allow WebView to open file://
*/
settings.allowFileAccess = true
/**
* Kotlin Playground downloads a file with Kotlin versions which generates a mistake,
* if allowUniversalAccessFromFileURLs is false
*/
settings.allowUniversalAccessFromFileURLs = value.settings.allowUniversalAccessFromFileURLs
}
}
}
}
Expand All @@ -96,7 +99,7 @@ constructor(
set(value) {
field = value
if (ViewCompat.isAttachedToWindow(this)) {
webView.webViewClient = requireNotNull(webViewClient)
webView?.webViewClient = requireNotNull(webViewClient)
}
}

Expand Down Expand Up @@ -131,30 +134,38 @@ constructor(
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
when (child?.id) {
textViewId -> {
textView = child as TextView
val textView = child as TextView
this.textView = textView
textView.setAttributes(attributes)
textView.movementMethod = LinkMovementMethod.getInstance()
}

webViewId -> {
webView = child as LatexWebView
val webView = child as LatexWebView
this.webView = webView
webView.attributes = attributes
if (ViewCompat.isAttachedToWindow(this)) {
webView.webViewClient = getOrCreateWebViewClient()
}
}
}
super.addView(child, index, params)
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()
webView.webViewClient = webViewClient ?: ExternalLinkWebViewClient(context)
webView?.webViewClient = getOrCreateWebViewClient()
}

override fun onDetachedFromWindow() {
webView.onImageClickListener = null
webView?.onImageClickListener = null
super.onDetachedFromWindow()
}

fun setText(text: String?) {
latexData = text?.let(latexTextMapper::mapToLatexText)
}

private fun getOrCreateWebViewClient(): WebViewClient =
webViewClient ?: ExternalLinkWebViewClient(context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.hyperskill.app.android.step.view.delegate.StepDelegate
import org.hyperskill.app.android.step.view.fragment.StepFragment
import org.hyperskill.app.android.step.view.model.StepCompletionHost
import org.hyperskill.app.android.step.view.model.StepCompletionView
import org.hyperskill.app.android.step_content_text.view.fragment.TextStepContentFragment
import org.hyperskill.app.android.step_practice.view.fragment.StepPracticeDetailsFragment
import org.hyperskill.app.android.step_quiz.view.dialog.RequestDailyStudyReminderDialogFragment
import org.hyperskill.app.android.step_quiz.view.factory.StepQuizFragmentFactory
import org.hyperskill.app.step.domain.model.Step
Expand Down Expand Up @@ -131,14 +131,14 @@ class StageStepWrapperFragment :
if (state is StepFeature.State.Data) {
(childFragmentManager.findFragmentByTag(STEP_QUIZ_FRAGMENT_TAG) as? StepCompletionView)
?.render(state.stepCompletionState.isPracticingLoading)
initStepTheoryFragment(state.step)
initStepTheoryFragment(state.step, stepRoute)
initStepQuizFragment(state.step, stepRoute)
}
}

private fun initStepTheoryFragment(step: Step) {
private fun initStepTheoryFragment(step: Step, stepRoute: StepRoute) {
setChildFragment(R.id.stageDescriptionContainer, STEP_DESCRIPTION_FRAGMENT_TAG) {
TextStepContentFragment.newInstance(step)
StepPracticeDetailsFragment.newInstance(step, stepRoute)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.hyperskill.app.android.step.view.delegate

import android.view.View
import org.hyperskill.app.android.ui.custom.ArrowImageView
import org.hyperskill.app.android.view.base.ui.extension.collapse
import org.hyperskill.app.android.view.base.ui.extension.expand

object CollapsibleStepBlockDelegate {
fun setupCollapsibleBlock(
arrowView: ArrowImageView,
headerView: View,
contentView: View,
onContentExpandChanged: (Boolean) -> Unit = {}
) {
headerView.setOnClickListener {
arrowView.changeState()
if (arrowView.isExpanded()) {
contentView.expand()
} else {
contentView.collapse()
}
onContentExpandChanged(arrowView.isExpanded())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.hyperskill.app.android.step_content_text.view.delegate

import android.content.Context
import android.view.LayoutInflater
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.hyperskill.app.android.R
import org.hyperskill.app.android.latex.view.widget.LatexView
import org.hyperskill.app.android.latex.view.widget.LatexWebView
import org.hyperskill.app.step.domain.model.Step

class TextStepContentDelegate(
fragmentLifecycle: Lifecycle
) : LifecycleObserver {

private var latexWebView: LatexWebView? = null

init {
fragmentLifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event.targetState == Lifecycle.State.DESTROYED) {
latexWebView = null
}
}
)
}

fun setup(
context: Context,
latexView: LatexView,
step: Step,
viewLifecycle: Lifecycle
) {
val webView = latexWebView ?: createLatexWebView(context, latexView)
this.latexWebView = webView
latexView.addView(webView)
latexView.setText(step.block.text)
viewLifecycle.addObserver(
object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event.targetState == Lifecycle.State.DESTROYED) {
latexView.removeView(latexWebView)
}
viewLifecycle.removeObserver(this)
}
}
)
}

private fun createLatexWebView(context: Context, latexView: LatexView): LatexWebView =
LayoutInflater
.from(context.applicationContext)
.inflate(R.layout.layout_latex_webview, latexView, false) as LatexWebView
}
Loading