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());
+}