diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/model/ScreenConfig.kt b/app/src/main/java/com/belmontCrest/cardCrafter/model/ScreenConfig.kt index 4b13a37..bab513e 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/model/ScreenConfig.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/model/ScreenConfig.kt @@ -99,7 +99,7 @@ fun getKatexMenuWidth(): Dp { } else -> { - 400.dp + 375.dp } } } else { diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/navigation/NavViewModel.kt b/app/src/main/java/com/belmontCrest/cardCrafter/navigation/NavViewModel.kt index 459a357..e78907f 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/navigation/NavViewModel.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/navigation/NavViewModel.kt @@ -14,9 +14,6 @@ import com.belmontCrest.cardCrafter.localDatabase.tables.Card import com.belmontCrest.cardCrafter.model.uiModels.StringVar import com.belmontCrest.cardCrafter.model.uiModels.SelectedCard import com.belmontCrest.cardCrafter.model.uiModels.SelectedKeyboard -import com.belmontCrest.cardCrafter.model.uiModels.SelectedKeyboard.Answer -import com.belmontCrest.cardCrafter.model.uiModels.SelectedKeyboard.Question -import com.belmontCrest.cardCrafter.model.uiModels.SelectedKeyboard.Step import com.belmontCrest.cardCrafter.model.uiModels.WhichDeck import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -215,7 +212,7 @@ class NavViewModel( } fun toggleKeyboard() { - _showKatexKeyboard.update { savedStateHandle[SHOW_KB] = !it;!it } + _showKatexKeyboard.update { savedStateHandle[SHOW_KB] = !it; !it } } fun resetOffset() { diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/KeyboardInputs.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/KeyboardInputs.kt index 03254e8..3a64d61 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/KeyboardInputs.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/KeyboardInputs.kt @@ -40,6 +40,7 @@ import com.belmontCrest.cardCrafter.ui.theme.textColor import com.belmontCrest.cardCrafter.uiFunctions.katex.menu.KaTeXMenu import com.belmontCrest.cardCrafter.uiFunctions.katex.isInside import com.belmontCrest.cardCrafter.uiFunctions.katex.katexMapper +import com.belmontCrest.cardCrafter.uiFunctions.katex.menu.IsInsideException import com.belmontCrest.cardCrafter.uiFunctions.katex.menu.SelectedAnnotation import com.belmontCrest.cardCrafter.uiFunctions.katex.menu.updateCursor import com.belmontCrest.cardCrafter.uiFunctions.katex.menu.updateNotation @@ -150,18 +151,24 @@ fun LatexKeyboard( } LaunchedEffect(kt) { val text = textFieldValue.text - if (kt.sa is SelectedAnnotation.CursorChange) { - textFieldValue = updateCursor(kt.sa, textFieldValue, text) - onIdle() - return@LaunchedEffect - } if (!textFieldValue.selection.collapsed) { Log.w(kk, "text field not collapsed.") return@LaunchedEffect } + if (kt.sa is SelectedAnnotation.CursorChange) { + try { + textFieldValue = updateCursor(kt.sa, textFieldValue, text) { onValueChanged(it) } + } catch (e : IsInsideException) { + Log.e(kk, "$e") + showToastMessage(context, "Cannot put notation inside a notation") + } + onIdle() + return@LaunchedEffect + } if (!isInside(text, text.length, textFieldValue.selection)) { if (kt.notation != null) { showToastMessage(context, "Make sure the symbol is between the delimiters.") + onIdle() } return@LaunchedEffect } diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/KatexMapping.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/KatexMapping.kt index e8cd5cd..5ff2cc3 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/KatexMapping.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/KatexMapping.kt @@ -47,7 +47,8 @@ fun katexMapper( return Pair( TextFieldValue( text = replaced, - selection = TextRange(insertionPoint) + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) ), replaced ) @@ -66,7 +67,8 @@ fun katexMapper( return Pair( TextFieldValue( text = replaced, - selection = TextRange(insertionPoint) + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) ), replaced ) @@ -93,7 +95,8 @@ fun katexMapper( return Pair( TextFieldValue( text = replaced, - selection = TextRange(insertionPoint) + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) ), replaced ) @@ -235,7 +238,8 @@ private fun katexWord( return Pair( TextFieldValue( text = replaced, - selection = TextRange(insertionPoint) + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) ), replaced ) diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKatexMenuWebView.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKatexMenuWebView.kt index b1d6506..79b3883 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKatexMenuWebView.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKatexMenuWebView.kt @@ -48,7 +48,7 @@ fun getWebView( fun onFracBinoSelected(eq: String) { Handler(Looper.getMainLooper()).postAtFrontOfQueue { val replaced = eq.replace("bra", "\\\\bra").replace("frac", "\\\\frac") - .replace("gen", "\\\\gen").replace("bin", "\\\\bin") + .replace("gen", "\\\\gen").replace("bin", "\\\\bin") onSelectNotation(replaced, SelectedAnnotation.EQ) } } @@ -68,10 +68,7 @@ fun getWebView( @JavascriptInterface fun onNormSelected(sel: String) { Handler(Looper.getMainLooper()).postAtFrontOfQueue { - val replaced = sel.replace("l", "\\\\l").replace("gt", "\\\\gt") - .replace("sqrt", "\\\\sqrt").replace("pi", "\\\\pi") - .replace("ti", "\\\\ti").replace("div", "\\\\div").replace("a", "") - .replace("{x}", "{}").replace("b", "") + val replaced = sel.convertNormSel() onSelectNotation(replaced, SelectedAnnotation.NORM) } } @@ -115,4 +112,42 @@ fun getWebView( loadUrl("file:///android_asset/katex-menu.html") } } -} \ No newline at end of file +} + +private val latexNormMap = mapOf( + // comparisons + "lt" to "\\\\lt", + "gt" to "\\\\gt", + "le" to "\\\\le", + "ge" to "\\\\ge", + + // fractions & roots + "times" to "\\\\times", + "div" to "\\\\div", + "sqrt{x}" to "\\\\sqrt{}", + "sqrt[a]{x}" to "\\\\sqrt[]{}", + + // constants + "pi" to "\\\\pi", + + // exponents & letters + "a^2" to "^2", + "a^{b}" to "^{}", + "|a|" to "||", + + // punctuation & parens + "(" to "(", + ")" to ")", + "," to ",", + "." to ".", + "=" to "=", + "{}" to "{}", + + // digits + "0" to "0", "1" to "1", "2" to "2", "3" to "3", "4" to "4", + "5" to "5", "6" to "6", "7" to "7", "8" to "8", "9" to "9" +) + + + +private fun String.convertNormSel(): String = latexNormMap[this] ?: this \ No newline at end of file diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKeyboardHtml.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKeyboardHtml.kt index ea57bbf..f29b734 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKeyboardHtml.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/BuildKeyboardHtml.kt @@ -149,7 +149,7 @@ private val BIG_OPERATORS = listOf( ) private val NORMAL_OPS_NUMS = listOf( - "x", "y", "a^2", "a^{b}", "(", ")", + "x", "y", "a^2", "a^{b}", "(", ")", "\\\\{\\\\}", "\\\\lt", "\\\\gt", "|a|", ",", "\\\\le", "\\\\ge", "\\\\sqrt{x}", "\\\\sqrt[a]{x}", "\\\\pi", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KaTeXMenu.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KaTeXMenu.kt index 3196e9c..25113b9 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KaTeXMenu.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KaTeXMenu.kt @@ -6,23 +6,33 @@ import android.webkit.WebView import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.height import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -32,6 +42,7 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView +import com.belmontCrest.cardCrafter.model.getIsLandScape import com.belmontCrest.cardCrafter.model.getKatexMenuWidth import com.belmontCrest.cardCrafter.ui.theme.GetUIStyle import kotlinx.parcelize.Parcelize @@ -62,12 +73,19 @@ sealed class SelectedAnnotation : Parcelable { sealed class CursorChange : SelectedAnnotation() { data object Forward : CursorChange() data object Backward : CursorChange() + data object Inline : CursorChange() + data object NewLine : CursorChange() + data object Delete : CursorChange() } @Parcelize data object Idle : SelectedAnnotation() } + +class IsInsideException : + Exception("You cannot put a notation equation inside a notation equation.") + //private const val KATEX_MENU = "KatexMenu" @OptIn(ExperimentalStdlibApi::class) @@ -79,6 +97,8 @@ fun KaTeXMenu( webView: WebView, scrollState: ScrollState, onCursorChange: (SelectedAnnotation.CursorChange) -> Unit ) { + + var expanded by rememberSaveable { mutableStateOf(false) } /* val maxY = getMaxHeight().value val maxX = getMaxWidth().value @@ -90,10 +110,92 @@ fun KaTeXMenu( Log.i(KATEX_MENU, "max height: $maxY") }*/ + val isLandscape = getIsLandScape() + val width = if (isLandscape) getKatexMenuWidth() / 2.5.dp else getKatexMenuWidth() / 2.dp + val offsetMod = + if (!isLandscape) Modifier.offset(y = -(126.dp)) else Modifier.offset(x = -(width.dp)) Box( modifier = modifier .offset { IntOffset(offsetProvider().x.roundToInt(), offsetProvider().y.roundToInt()) } ) { + if (expanded) { + Column( + modifier = offsetMod + .width(width.dp) + .height(126.dp) + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + onOffset(dragAmount) + } + } + .background(getUIStyle.katexMenuHeaderColor(), RoundedCornerShape(12.dp)) + .border(1.5.dp, getUIStyle.defaultIconColor(), RoundedCornerShape(12.dp)), + verticalArrangement = Arrangement.SpaceEvenly + ) { + Row( + modifier = Modifier + .align(Alignment.Start) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onCursorChange(SelectedAnnotation.CursorChange.Backward) }, + ) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null, + modifier = Modifier.size(25.dp) + ) + } + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onCursorChange(SelectedAnnotation.CursorChange.Forward) }, + ) { + Icon( + Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, + modifier = Modifier.size(25.dp) + ) + } + Text( + text = "delete", + modifier = Modifier + .clickable { + onCursorChange(SelectedAnnotation.CursorChange.Delete) + }, + textAlign = TextAlign.End, + fontSize = 14.sp, + color = getUIStyle.titleColor() + ) + + } + HorizontalDivider(color = getUIStyle.defaultIconColor()) + Text( + text = "Start Inline", + modifier = Modifier + .clickable { + onCursorChange(SelectedAnnotation.CursorChange.Inline) + } + .fillMaxWidth(), + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = getUIStyle.titleColor() + ) + HorizontalDivider(color = getUIStyle.defaultIconColor()) + Text( + text = "Start New Line", + modifier = Modifier + .clickable { + onCursorChange(SelectedAnnotation.CursorChange.NewLine) + } + .fillMaxWidth(), + textAlign = TextAlign.Center, + fontSize = 16.sp, + color = getUIStyle.titleColor() + ) + } + } Box( modifier = Modifier .width(getKatexMenuWidth()) @@ -123,20 +225,16 @@ fun KaTeXMenu( } .border(1.5.dp, getUIStyle.defaultIconColor()) ) { - - Row( - modifier = Modifier.align(Alignment.TopStart), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { onCursorChange(SelectedAnnotation.CursorChange.Backward) }, - ) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } - IconButton( - onClick = { onCursorChange(SelectedAnnotation.CursorChange.Forward) }, - ) { Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null) } - } - + Text( + text = if (!expanded) "Expand" else "Minimize", + modifier = Modifier + .padding(start = 4.dp) + .align(Alignment.TopStart) + .clickable { expanded = !expanded }, + textAlign = TextAlign.Start, + fontSize = 16.sp, + color = getUIStyle.titleColor() + ) Text( text = "Drag here", modifier = Modifier.align(Alignment.Center), diff --git a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KatexSA.kt b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KatexSA.kt index 2a9aa15..5b83dce 100644 --- a/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KatexSA.kt +++ b/app/src/main/java/com/belmontCrest/cardCrafter/uiFunctions/katex/menu/KatexSA.kt @@ -3,6 +3,7 @@ package com.belmontCrest.cardCrafter.uiFunctions.katex.menu import android.util.Log import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import com.belmontCrest.cardCrafter.uiFunctions.katex.isInside /** * Add the notation to the given text field and update the textFieldValue @@ -87,11 +88,14 @@ fun updateNotation( return updateNotation(textFieldValue, text, kk, notation, -1) { onValueChanged(it) } } else if (notation.startsWith("\\\\sqrt")) { val offset = - if (notation.firstOrNull { it == '[' } != null) -3 - else -1 + if (notation.firstOrNull { it == '[' } != null) -3 else -1 return updateNotation(textFieldValue, text, kk, notation, offset) { onValueChanged(it) } + } else if (notation.startsWith("{}")) { + return updateNotation(textFieldValue, text, kk, notation, -1) { + onValueChanged(it) + } } else { return updateNotation(textFieldValue, text, kk, notation, 0) { onValueChanged(it) } } @@ -107,7 +111,8 @@ fun updateNotation( } fun updateCursor( - cursor: SelectedAnnotation.CursorChange, textFieldValue: TextFieldValue, text: String + cursor: SelectedAnnotation.CursorChange, textFieldValue: TextFieldValue, + text: String, onValueChanged: (String) -> Unit ): TextFieldValue { when (cursor) { SelectedAnnotation.CursorChange.Backward -> { @@ -120,5 +125,58 @@ fun updateCursor( TextRange((textFieldValue.selection.start + 1).coerceAtMost(text.length)) return TextFieldValue(text, textRange, textRange) } + + SelectedAnnotation.CursorChange.Inline -> { + if (isInside(text, text.length, textFieldValue.selection)) throw IsInsideException() + val replacement = "\\\\(\\\\)" + val startIndex = textFieldValue.selection.start + val insertionPoint = startIndex + replacement.length - 3 + val replaced = buildString { + append(text.substring(0, startIndex)) + append(replacement) + append(text.substring(startIndex)) + } + onValueChanged(replaced) + return TextFieldValue( + text = replaced, + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) + ) + } + + SelectedAnnotation.CursorChange.NewLine -> { + if (isInside(text, text.length, textFieldValue.selection)) throw IsInsideException() + val replacement = "$$$$" + val startIndex = textFieldValue.selection.start + val insertionPoint = startIndex + replacement.length - 2 + val replaced = buildString { + append(text.substring(0, startIndex)) + append(replacement) + append(text.substring(startIndex)) + } + onValueChanged(replaced) + return TextFieldValue( + text = replaced, + selection = TextRange(insertionPoint), + composition = TextRange(insertionPoint) + ) + } + + SelectedAnnotation.CursorChange.Delete -> { + if (text.isBlank()) return textFieldValue + val startIndex = textFieldValue.selection.start + if (startIndex == 0) return textFieldValue + val replaced = buildString { + append(text.substring(0, (startIndex - 1))) + append(text.substring(startIndex)) + } + val textRange = TextRange((textFieldValue.selection.start - 1).coerceAtLeast(0)) + onValueChanged(replaced) + return TextFieldValue( + text = replaced, + selection = textRange, + composition = textRange + ) + } } } \ No newline at end of file