diff --git a/app/build.gradle b/app/build.gradle index ddc3d047..bb532ee1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -68,4 +68,5 @@ dependencies { implementation 'com.intuit.ssp:ssp-android:1.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' } \ No newline at end of file diff --git a/app/src/main/java/com/fintamath/calculator/Approximator.kt b/app/src/main/java/com/fintamath/calculator/Approximator.kt new file mode 100644 index 00000000..9ab0a350 --- /dev/null +++ b/app/src/main/java/com/fintamath/calculator/Approximator.kt @@ -0,0 +1,16 @@ +package com.fintamath.calculator + +class Approximator { + + external fun approximate(exprStr: String, varStr: String, valStr: String): String + + external fun getVariableCount(exprStr: String): Int + + external fun getLastVariable(exprStr: String): String + + companion object { + init { + System.loadLibrary("fintamath_android") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fintamath/calculator/Calculator.kt b/app/src/main/java/com/fintamath/calculator/Calculator.kt index a698cccc..e60d4834 100644 --- a/app/src/main/java/com/fintamath/calculator/Calculator.kt +++ b/app/src/main/java/com/fintamath/calculator/Calculator.kt @@ -16,6 +16,12 @@ internal class Calculator( external fun setPrecision(int: Int) + external fun approximate(exprStr: String, varStr: String, valStr: String): String + + external fun getVariableCount(exprStr: String): Int + + external fun getLastVariable(exprStr: String): String + private fun onCalculated(str: String) { calculatedCallback.invoke(listOf(*str.split("\n").toTypedArray())) } diff --git a/app/src/main/java/com/fintamath/calculator/CalculatorProcessor.kt b/app/src/main/java/com/fintamath/calculator/CalculatorProcessor.kt index a69bb724..f1c15177 100644 --- a/app/src/main/java/com/fintamath/calculator/CalculatorProcessor.kt +++ b/app/src/main/java/com/fintamath/calculator/CalculatorProcessor.kt @@ -50,6 +50,13 @@ class CalculatorProcessor( fun setPrecision(precision: Int) = calculator.setPrecision(precision) + fun approximate(exprStr: String, varStr: String, valStr: String): String = + calculator.approximate(exprStr, varStr, valStr) + + fun getVariableCount(exprStr: String): Int = calculator.getVariableCount(exprStr) + + fun getLastVariable(exprStr: String): String = calculator.getLastVariable(exprStr) + private fun onCalculated(result: List) { callbacksThread { isCalculating.set(false) diff --git a/app/src/main/java/com/fintamath/fragment/calculator/CalculatorFragment.kt b/app/src/main/java/com/fintamath/fragment/calculator/CalculatorFragment.kt index 3f8ddc0e..83b43fa1 100644 --- a/app/src/main/java/com/fintamath/fragment/calculator/CalculatorFragment.kt +++ b/app/src/main/java/com/fintamath/fragment/calculator/CalculatorFragment.kt @@ -2,21 +2,27 @@ package com.fintamath.fragment.calculator import android.graphics.drawable.ColorDrawable import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import com.fintamath.R import com.fintamath.calculator.CalculatorProcessor import com.fintamath.databinding.FragmentCalculatorBinding import com.fintamath.storage.HistoryStorage -import com.fintamath.storage.CalculatorInputStorage +import com.fintamath.storage.CalculatorStorage import com.fintamath.storage.MathTextData import com.fintamath.storage.SettingsStorage import com.fintamath.widget.keyboard.Keyboard import com.fintamath.widget.keyboard.KeyboardView +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import java.math.BigDecimal import java.util.Timer import java.util.TimerTask import java.util.concurrent.atomic.AtomicBoolean @@ -41,6 +47,8 @@ class CalculatorFragment : Fragment() { private var wereSettingsUpdated = AtomicBoolean(false) + private var drawGraphJob: Job? = null + private val maxSolutionLength = 2000 private val inTextViewPreloadString = "abc * 123 Pi E I Inf ComplexInf () sqrt() abs() floor() ceil() derivative(x,x)" @@ -78,8 +86,8 @@ class CalculatorFragment : Fragment() { updateSettings() if (inTextViewState.get() == InTextViewState.Ready.value) { - if (viewBinding.inTextView.text != CalculatorInputStorage.mathTextData.text) { - viewBinding.inTextView.text = CalculatorInputStorage.mathTextData.text + if (viewBinding.inTextView.text != CalculatorStorage.inputMathTextData.text) { + viewBinding.inTextView.text = CalculatorStorage.inputMathTextData.text } if (viewBinding.outSolutionView.isShowingLoading() || wereSettingsUpdated.get()) { @@ -103,7 +111,7 @@ class CalculatorFragment : Fragment() { private fun initMathTexts() { viewBinding.inTextLayout.setOnTouchListener { _, event -> touchInText(event) } - viewBinding.inTextView.text = CalculatorInputStorage.mathTextData.text + viewBinding.inTextView.text = CalculatorStorage.inputMathTextData.text viewBinding.inTextView.setOnTextChangedListener { _, text -> onInTextChange(text) } viewBinding.inTextView.setOnFocusChangeListener { _, state -> onInTextFocusChange(state) } } @@ -174,10 +182,11 @@ class CalculatorFragment : Fragment() { } private fun initBarButtons() { -// viewBinding.cameraButton.setOnClickListener { showCameraFragment() } // TODO: uncomment when camera is implemented + // viewBinding.cameraButton.setOnClickListener { showCameraFragment() } // TODO: uncomment when camera is implemented viewBinding.historyButton.setOnClickListener { showHistoryFragment() } viewBinding.settingsButton.setOnClickListener { showSettingsFragment() } viewBinding.aboutButton.setOnClickListener { showAboutFragment() } + viewBinding.graphButton.setOnClickListener { showGraphFragment() } } private fun updateSettings() { @@ -206,7 +215,7 @@ class CalculatorFragment : Fragment() { } } - CalculatorInputStorage.mathTextData = MathTextData(text) + CalculatorStorage.inputMathTextData = MathTextData(text) cancelSaveToHistoryTask() viewBinding.inTextViewHint.visibility = if (viewBinding.inTextView.text.isNotEmpty()) @@ -245,6 +254,8 @@ class CalculatorFragment : Fragment() { viewBinding.outSolutionView.showFailedToSolve() } else { val cutSolutionTexts = cutSolutionTexts(texts) + val firstSolutionText = cutSolutionTexts.first() + CalculatorStorage.outputMathTextData.text = firstSolutionText if (countTextsLength(cutSolutionTexts) > maxSolutionLength) { viewBinding.outSolutionView.showCharacterLimitExceeded() @@ -314,6 +325,10 @@ class CalculatorFragment : Fragment() { showFragment(R.id.action_calculatorFragment_to_settingsFragment) } + private fun showGraphFragment() { + showFragment(R.id.action_calculatorFragment_to_graphFragment) + } + private fun showFragment(navigationId: Int) { runSaveToHistoryTask() viewBinding.inTextView.clearFocusInWeb() diff --git a/app/src/main/java/com/fintamath/fragment/graph/GraphFragment.kt b/app/src/main/java/com/fintamath/fragment/graph/GraphFragment.kt new file mode 100644 index 00000000..e5f3c401 --- /dev/null +++ b/app/src/main/java/com/fintamath/fragment/graph/GraphFragment.kt @@ -0,0 +1,161 @@ +package com.fintamath.fragment.graph + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.findNavController +import com.fintamath.R +import com.fintamath.calculator.Approximator +import com.fintamath.databinding.FragmentGraphBinding +import com.fintamath.storage.CalculatorStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import java.math.BigDecimal + +class GraphFragment : Fragment() { + + private lateinit var viewBinding: FragmentGraphBinding + private lateinit var approximator: Approximator + + private var currentMathText = CalculatorStorage.outputMathTextData.text + + private val drawGraphScope = CoroutineScope(Job() + Dispatchers.Main) + private var drawGraphJob: Job? = null + + private val initialMinX = BigDecimal(-10) + private val initialMaxX = BigDecimal(10) + private val graphPointNum = 200 + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + viewBinding = FragmentGraphBinding.inflate(inflater, container, false) + + initBarButtons() + initApproximator() + + viewBinding.graphView.setOnScrollOrScale { + if (currentMathText != CalculatorStorage.outputMathTextData.text) { + currentMathText = CalculatorStorage.outputMathTextData.text + viewBinding.graphView.clearGraph() + } + + drawGraph(viewBinding.graphView.getMinX(), viewBinding.graphView.getMaxX()) + } + + drawGraph(initialMinX, initialMaxX) + + return viewBinding.root + } + + private fun initBarButtons() { + viewBinding.calculatorButton.setOnClickListener { showCalculatorFragment() } + viewBinding.historyButton.setOnClickListener { showHistoryFragment() } + // viewBinding.cameraButton.setOnClickListener { showCameraFragment() } // TODO: uncomment when camera is implemented{ + viewBinding.settingsButton.setOnClickListener { showSettingsFragment() } + viewBinding.aboutButton.setOnClickListener { showAboutFragment() } + } + + private fun initApproximator() { + approximator = Approximator() + } + + private fun drawGraph(minX: BigDecimal, maxX: BigDecimal) { + drawGraph(CalculatorStorage.outputMathTextData.text, minX, maxX) + } + + private fun drawGraph(firstSolutionText: String, min: BigDecimal, max: BigDecimal) { + drawGraphJob?.cancel() + + drawGraphJob = drawGraphScope.launch { + if (approximator.getVariableCount(firstSolutionText) != 1) { + return@launch + } + + val varName = approximator.getLastVariable(firstSolutionText) + val mid = (min + max).divide(BigDecimal(2)) + + drawGraphPoint(firstSolutionText, varName, mid) + + val delta = (max - min).divide(BigDecimal(graphPointNum)) + var bottom = mid - delta + var top = mid + delta + + while (bottom >= min) { + yield() + + drawGraphPoint(firstSolutionText, varName, bottom) + drawGraphPoint(firstSolutionText, varName, top) + + bottom -= delta + top += delta + } + } + } + + private fun drawGraphPoint( + firstSolutionText: String, + varName: String, + varValue: BigDecimal + ) { + if (viewBinding.graphView.hasPoint(varValue)) { + return + } + + val approxValueStr = approximator.approximate( + firstSolutionText, + varName, + toFintamathReal(varValue) + ) + + if (approxValueStr.isEmpty()) { + return + } + + viewBinding.graphView.addPoint(varValue, toBigDecimal(approxValueStr)) + } + + private fun toFintamathReal(bigDecimal: BigDecimal): String { + return bigDecimal.toString().replace("E", "*10^") + } + + private fun toBigDecimal(fintamathReal: String): BigDecimal { + return BigDecimal(fintamathReal.replace("*10^", "E")) + } + + private fun showCalculatorFragment() { + executeBack() + } + + private fun showHistoryFragment() { + executeBack() + showFragment(R.id.action_calculatorFragment_to_historyFragment) + } + + private fun showCameraFragment() { + executeBack() + showFragment(R.id.action_calculatorFragment_to_cameraFragment) + } + + private fun showAboutFragment() { + showFragment(R.id.action_graphFragment_to_aboutFragment) + } + + private fun showSettingsFragment() { + showFragment(R.id.action_graphFragment_to_settingsFragment) + } + + private fun showFragment(navigationId: Int) { + viewBinding.root.findNavController().navigate(navigationId) + } + + private fun executeBack() { + viewBinding.root.findNavController().navigateUp() + } +} diff --git a/app/src/main/java/com/fintamath/fragment/history/HistoryFragment.kt b/app/src/main/java/com/fintamath/fragment/history/HistoryFragment.kt index 41c37933..03cbc930 100644 --- a/app/src/main/java/com/fintamath/fragment/history/HistoryFragment.kt +++ b/app/src/main/java/com/fintamath/fragment/history/HistoryFragment.kt @@ -13,10 +13,7 @@ import com.fintamath.R import com.fintamath.databinding.FragmentHistoryBinding import com.fintamath.storage.HistoryStorage import com.fintamath.storage.MathTextData -import com.fintamath.storage.CalculatorInputStorage -import java.util.Timer -import java.util.TimerTask -import kotlin.concurrent.schedule +import com.fintamath.storage.CalculatorStorage class HistoryFragment : Fragment() { @@ -52,7 +49,8 @@ class HistoryFragment : Fragment() { private fun initBarButtons() { viewBinding.calculatorButton.setOnClickListener { showCalculatorFragment() } -// viewBinding.cameraButton.setOnClickListener { showCameraFragment() } // TODO: uncomment when camera is implemented + viewBinding.graphButton.setOnClickListener { showGraphFragment() } + // viewBinding.cameraButton.setOnClickListener { showCameraFragment() } // TODO: uncomment when camera is implemented{ viewBinding.settingsButton.setOnClickListener { showSettingsFragment() } viewBinding.aboutButton.setOnClickListener { showAboutFragment() } } @@ -73,7 +71,7 @@ class HistoryFragment : Fragment() { } private fun onCalculate(text: String) { - CalculatorInputStorage.mathTextData = MathTextData(text) + CalculatorStorage.inputMathTextData = MathTextData(text) showCalculatorFragment() } @@ -81,6 +79,11 @@ class HistoryFragment : Fragment() { executeBack() } + private fun showGraphFragment() { + executeBack() + showFragment(R.id.action_calculatorFragment_to_graphFragment) + } + private fun showCameraFragment() { executeBack() showFragment(R.id.action_calculatorFragment_to_cameraFragment) diff --git a/app/src/main/java/com/fintamath/storage/CalculatorInputStorage.kt b/app/src/main/java/com/fintamath/storage/CalculatorInputStorage.kt deleted file mode 100644 index 8bf2cd6c..00000000 --- a/app/src/main/java/com/fintamath/storage/CalculatorInputStorage.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.fintamath.storage - -object CalculatorInputStorage { - var mathTextData: MathTextData = MathTextData("") -} diff --git a/app/src/main/java/com/fintamath/storage/CalculatorStorage.kt b/app/src/main/java/com/fintamath/storage/CalculatorStorage.kt new file mode 100644 index 00000000..2788dd16 --- /dev/null +++ b/app/src/main/java/com/fintamath/storage/CalculatorStorage.kt @@ -0,0 +1,6 @@ +package com.fintamath.storage + +object CalculatorStorage { + var inputMathTextData: MathTextData = MathTextData("") + var outputMathTextData: MathTextData = MathTextData("") +} diff --git a/app/src/main/java/com/fintamath/widget/graph/GraphGrid.kt b/app/src/main/java/com/fintamath/widget/graph/GraphGrid.kt new file mode 100644 index 00000000..05d30792 --- /dev/null +++ b/app/src/main/java/com/fintamath/widget/graph/GraphGrid.kt @@ -0,0 +1,81 @@ +package com.fintamath.widget.graph + +import android.graphics.Canvas +import kotlin.math.min + +class GraphGrid() { + + private var offsetX: Float = 0f + private var offsetY: Float = 0f + + private var width: Float = 0f + private var height: Float = 0f + + private var gridLines : MutableList = ArrayList() + + private var cellSize: Float = 0.0f + private var cellDelta: Float = 0.0f + private var minimalX: Float = 0.0f + private var minimalY: Float = 0.0f + + fun onDraw(canvas: Canvas) { + for (gridLine in gridLines) { + gridLine.onDraw(canvas, cellSize, cellDelta >= 1) + } + } + + fun update(width: Float, + height: Float, + offsetX: Float, + offsetY: Float, + cellCount: Int, + cellDelta: Float) { + + this.gridLines = ArrayList() + this.width = width + this.height = height + this.cellSize = min(this.height, this.width) / cellCount + + this.offsetX = offsetX + this.offsetY = offsetY + this.cellDelta = cellDelta + this.minimalY = countMinimalY().toFloat() + this.minimalX = countMinimalX().toFloat() + + addVerticalLines() + addHorizontalLines() + } + + fun getCellSize(): Float { + return cellSize + } + + private fun countMinimalX() : Int { + return ((-width / 2 - offsetX) / cellSize).toInt() + } + + private fun countMinimalY() : Int { + return ((offsetY + height / 2 ) / cellSize).toInt() + } + + private fun addVerticalLines() { + var xCoord = (offsetX + width / 2) % cellSize + var value: Float = minimalX * cellDelta + while (xCoord < width) { + gridLines.add(GridLine(width, height, xCoord, offsetY + height / 2, false, value)) + xCoord += cellSize + value += cellDelta + } + } + + private fun addHorizontalLines() { + var yCoord = (offsetY + height / 2) % cellSize + var value: Float = minimalY * cellDelta + while (yCoord < height) { + gridLines.add(GridLine(width, height, offsetX + width / 2, yCoord, true, value)) + yCoord += cellSize + value -= cellDelta + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/fintamath/widget/graph/GraphView.kt b/app/src/main/java/com/fintamath/widget/graph/GraphView.kt new file mode 100644 index 00000000..ccfa7fe4 --- /dev/null +++ b/app/src/main/java/com/fintamath/widget/graph/GraphView.kt @@ -0,0 +1,221 @@ +package com.fintamath.widget.graph + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.view.MotionEvent +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.ScaleGestureDetector +import android.view.View +import java.math.BigDecimal +import kotlin.math.abs + +@SuppressLint("ClickableViewAccessibility") +class GraphView( + ctx: Context, + attrs: AttributeSet +) : View(ctx, attrs) { + + private var lockScrollScale = false + + private var points : MutableMap = mutableMapOf() + + private val axisPaint = Paint().apply { + color = Color.LTGRAY + strokeWidth = 3f + } + + private val pointPaint = Paint().apply { + color = Color.CYAN + strokeWidth = 4f + } + + private var offsetX = 0.0f + private var offsetY = 0.0f + + private val minCellCount: Int = 10 + private var currCellCount = minCellCount + + private var updateLambda: ()->Unit = {} + + private var graphGrid: GraphGrid = GraphGrid() + private var cellDelta = 1f + private var scaleFactor = 1f + + private val scrollDetector = GestureDetector(ctx, object : GestureDetector.SimpleOnGestureListener() { + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (e1 == null) { + return false + } + + when (e2.action) { + MotionEvent.ACTION_MOVE -> { + offsetX -= distanceX + offsetY -= distanceY + } + } + + onScrollOrScale() + invalidate() + + return true + } + }) + + private val scaleDetector = ScaleGestureDetector(ctx, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + scaleFactor /= detector.scaleFactor + + updateScale() + onScrollOrScale() + invalidate() + + return true + } + }) + + init { + setOnTouchListener {_, event -> + scrollDetector.onTouchEvent(event) + scaleDetector.onTouchEvent(event) + } + } + + fun setOnScrollOrScale(onScrollOnScale: ()->Unit) { + updateLambda = onScrollOnScale + } + + fun clearGraph() { + points.clear() + } + + fun addPoint(x: BigDecimal, y: BigDecimal) { + points[x] = y + invalidate() + } + + fun hasPoint(x: BigDecimal): Boolean { + return points.contains(x) + } + + private fun onScrollOrScale() { + updateLambda() + } + + private fun updateScale() { + if (scaleFactor < 0.5f) { + scaleFactor = 1f + cellDelta /=2 + } + + if (scaleFactor > 1.5f) { + scaleFactor = 1f + cellDelta *= 2 + + } + + currCellCount = (minCellCount * scaleFactor).toInt() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val width = width.toFloat() + val height = height.toFloat() + + graphGrid.update(width, height, offsetX, offsetY, currCellCount, cellDelta) + graphGrid.onDraw(canvas) + + if ((offsetX <= 0 && abs(offsetX) < (width / 2 - 40f)) || (offsetX > 0 && abs(offsetX) < (width / 2 - 20f))) { + drawVerticalAxis(canvas) + } + + if (abs(offsetY) < height / 2) { + drawHorizontalAxis(canvas) + } + + drawPoints(canvas) + } + + private fun drawHorizontalAxis(canvas: Canvas) { + val offsetHeight = height.toFloat() / 2 + offsetY + val width = width.toFloat() + canvas.drawLine(0f, offsetHeight, width, offsetHeight, axisPaint) + } + + private fun drawVerticalAxis(canvas: Canvas) { + val height = height.toFloat() + val offsetWidth = width.toFloat() / 2 + offsetX + canvas.drawLine(offsetWidth, 0f, offsetWidth, height, axisPaint) + } + + private fun drawPoints(canvas: Canvas) { + var oldX: BigDecimal? = null + var oldY: BigDecimal? = null + + for (x in points.keys.toList().sorted()) { + val y = points[x] + + if (oldX == null) { + oldX = x + oldY = y + if (y != null) { + drawPointOnCanvas(x, y, canvas) + } + continue + } + if (y != null && oldY != null) { + val delta = abs(oldY.toFloat() - y.toFloat()) * graphGrid.getCellSize() / cellDelta + if (delta > height) { + oldX = x + oldY = y + continue + } + drawLineOnCanvas(x, y, oldX, oldY, canvas) + oldX = x + oldY = y + } + } + } + + private fun drawPointOnCanvas(x: BigDecimal, y: BigDecimal, canvas: Canvas) { + val cellSize = graphGrid.getCellSize() + canvas.drawPoint(offsetX + width/2 + x.toFloat() * cellSize / cellDelta, + offsetY + height/2 - y.toFloat() * cellSize / cellDelta, pointPaint) + } + + private fun drawLineOnCanvas(x1: BigDecimal, y1:BigDecimal, x2:BigDecimal, y2:BigDecimal, canvas: Canvas) { + val cellSize = graphGrid.getCellSize() + canvas.drawLine(offsetX + width/2 + x1.toFloat() * cellSize / cellDelta, + offsetY + height/2 - y1.toFloat() * cellSize / cellDelta, + offsetX + width/2 + x2.toFloat() * cellSize / cellDelta, + offsetY + height/2 - y2.toFloat() * cellSize / cellDelta, + pointPaint) + } + + fun getMinX(): BigDecimal { + return canvasXToGraphX(BigDecimal(0)) + } + + fun getMaxX() : BigDecimal { + return canvasXToGraphX(BigDecimal(width)) + } + + private fun canvasXToGraphX(canvasX: BigDecimal) : BigDecimal { + return BigDecimal((cellDelta/graphGrid.getCellSize() * (canvasX.toFloat() - offsetX - width/2)).toString()) + } + + private fun graphXToCanvasX(graphX: BigDecimal) : BigDecimal{ + return BigDecimal((offsetX + width/2 + graphX.toFloat() * graphGrid.getCellSize() / cellDelta).toString()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/fintamath/widget/graph/GridLine.kt b/app/src/main/java/com/fintamath/widget/graph/GridLine.kt new file mode 100644 index 00000000..3635c4cc --- /dev/null +++ b/app/src/main/java/com/fintamath/widget/graph/GridLine.kt @@ -0,0 +1,37 @@ +package com.fintamath.widget.graph + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint + +class GridLine( + private val width: Float, + private val height: Float, + private val x: Float, + private val y: Float, + private val isHorizontal: Boolean, + private val value: Float +) { + + private val gridPaint = Paint().apply { + color = Color.DKGRAY + strokeWidth = 2f + } + + private val textPaint = Paint().apply { + color = Color.LTGRAY + textSize = 25f + textAlign = Paint.Align.RIGHT + } + + fun onDraw(canvas: Canvas, cellSize: Float, drawInt : Boolean) { + if (isHorizontal) { + canvas.drawLine(0f, y, width, y, gridPaint) + if (value != 0f) + canvas.drawText(if (drawInt) value.toInt().toString() else value.toString(), (x - cellSize / 4).coerceIn(cellSize, width - cellSize / 4), y + cellSize / 4, textPaint) + } else { + canvas.drawLine(x, 0f, x, height, gridPaint) + canvas.drawText(if (drawInt) value.toInt().toString() else value.toString(), x - cellSize / 8, (y + cellSize / 2).coerceIn(cellSize / 2, height - cellSize / 4), textPaint) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_graph.xml b/app/src/main/res/drawable/ic_graph.xml new file mode 100644 index 00000000..b64ead00 --- /dev/null +++ b/app/src/main/res/drawable/ic_graph.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_calculator.xml b/app/src/main/res/layout/fragment_calculator.xml index 7c184b79..a8d9dac1 100644 --- a/app/src/main/res/layout/fragment_calculator.xml +++ b/app/src/main/res/layout/fragment_calculator.xml @@ -42,6 +42,18 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index 3599199e..f1924b8a 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -42,6 +42,18 @@ + + + + + + @@ -61,6 +65,22 @@ + + + + + + + + Back Camera History + Graph Edit Settings Bookmark diff --git a/lib/src/Fintamath.cpp b/lib/src/Fintamath.cpp index 1b2588da..a5273caf 100644 --- a/lib/src/Fintamath.cpp +++ b/lib/src/Fintamath.cpp @@ -2,6 +2,7 @@ #include "fintamath/expressions/Expression.hpp" #include "fintamath/expressions/ExpressionFunctions.hpp" #include "fintamath/expressions/ExpressionParser.hpp" +#include "fintamath/numbers/Real.hpp" #include #include @@ -25,6 +26,10 @@ static unsigned currPrecision = 10; static pid_t calcPid = -1; static auto *solutionStrShared = (char *)mmap(nullptr, maxSolutionLength, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); +static std::string exprStrCached; +static Expression exprCached; +static std::vector exprVariablesCached; + std::string makeOutResult(const std::string &res) { return res + "\n"; } @@ -62,6 +67,25 @@ void stopCurrentCalculations() { } } +void updateCachedExpression(const std::string &exprStr) { + if (exprStrCached == exprStr) { + return; + } + + try { + exprStrCached = exprStr; + exprCached = Expression(exprStrCached); + exprVariablesCached = exprCached.getVariables(); + } + catch (const std::exception &exc) { + __android_log_print(ANDROID_LOG_DEBUG, loggerTag, "%s", exc.what()); + + exprStrCached = ""; + exprCached = 0; + exprVariablesCached.clear(); + } +} + extern "C" JNIEXPORT void Java_com_fintamath_calculator_Calculator_calculate(JNIEnv *env, jobject instance, jstring inJStr) { stopCurrentCalculations(); @@ -129,3 +153,51 @@ extern "C" JNIEXPORT void Java_com_fintamath_calculator_Calculator_setPrecision( currPrecision = inPrecision; } } + +extern "C" JNIEXPORT jstring Java_com_fintamath_calculator_Approximator_approximate(JNIEnv *env, jobject instance, jstring exprJStr, jstring varJStr, jstring valJStr) { + std::string exprStr = env->GetStringUTFChars(exprJStr, nullptr); + std::string varStr = env->GetStringUTFChars(varJStr, nullptr); + std::string valStr = env->GetStringUTFChars(valJStr, nullptr); + + updateCachedExpression(exprStr); + + try { + constexpr unsigned approxPrecision = 100; + constexpr unsigned outputPrecision = 5; + + Real::ScopedSetPrecision setPrecision(approxPrecision); + + Variable var(varStr); + Real val(valStr); + + Expression approxExpr = exprCached; + approxExpr.setVariable(var, val); + approxExpr = approxExpr.approximate(); + + if (auto res = convert(*approxExpr.toMinimalObject())) { + return env->NewStringUTF(res->toString(outputPrecision).c_str()); + } + } + catch (const std::exception &exc) { + __android_log_print(ANDROID_LOG_DEBUG, loggerTag, "%s", exc.what()); + } + + return env->NewStringUTF(""); +} + +extern "C" JNIEXPORT jint Java_com_fintamath_calculator_Approximator_getVariableCount(JNIEnv *env, jobject instance, jstring exprJStr) { + std::string exprStr = env->GetStringUTFChars(exprJStr, nullptr); + updateCachedExpression(exprStr); + return jint(exprVariablesCached.size()); +} + +extern "C" JNIEXPORT jstring Java_com_fintamath_calculator_Approximator_getLastVariable(JNIEnv *env, jobject instance, jstring exprJStr) { + std::string exprStr = env->GetStringUTFChars(exprJStr, nullptr); + updateCachedExpression(exprStr); + + if (exprVariablesCached.empty()) { + return env->NewStringUTF(""); + } + + return env->NewStringUTF(exprVariablesCached.back().toString().c_str()); +}