Skip to content

Commit

Permalink
Merge pull request #191 from aritra-tech/develop
Browse files Browse the repository at this point in the history
v1.6.1
  • Loading branch information
aritra-tech authored Oct 26, 2023
2 parents 14c07ea + 2aea013 commit 3e3ab57
Show file tree
Hide file tree
Showing 25 changed files with 967 additions and 309 deletions.
30 changes: 18 additions & 12 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 0 additions & 39 deletions app/google-services.json

This file was deleted.

128 changes: 128 additions & 0 deletions app/src/main/java/com/aritra/notify/components/drawing/Drawable.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.aritra.notify.components.drawing

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import com.aritra.notify.utils.pos
import com.aritra.notify.utils.size
import kotlin.math.PI
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin

/**
* Specifies that the class can draw something
*/
abstract class Drawable(
val start: Offset,
open var end: Offset,
val stroke: Stroke,
val color: Color,
) {

constructor(props: DrawableProperties) : this(
props.start,
props.end,
props.stroke,
props.color
)

/**
* Draws the drawable on the canvas
*
* @param scope the object used to draw the drawable
* */
abstract fun draw(scope: DrawScope)

override fun toString() = "${this::class.simpleName} from start: $start to end: $end"
}

data class DrawableProperties(
val start: Offset,
val end: Offset,
val stroke: Stroke,
val color: Color,
)

class Pencil(properties: DrawableProperties) : Drawable(properties) {
private val path = Path()
override var end: Offset = start.x pos start.y
set(value) {
path.quadraticBezierTo(
end.x,
end.y,
((end.x + value.x) / 2),
((end.y + value.y) / 2)
)
path.lineTo(value.x, value.y)
field = value
}

init {
path.moveTo(start.x, start.y)
}

override fun draw(scope: DrawScope) {
scope.drawPath(path, color, style = stroke)
}
}

open class Line(properties: DrawableProperties) : Drawable(properties) {

override fun draw(scope: DrawScope) {
scope.drawLine(
color,
start,
end,
stroke.width,
stroke.cap,
stroke.pathEffect
)
}
}

class Arrow(properties: DrawableProperties) : Line(properties) {

override fun draw(scope: DrawScope) {
// calls the super method to draw the line
super.draw(scope)
// draw the arrow head
val (radius, angle) = 30f to 60f
val angleInRad = PI * angle / 180f
val lineAngle = atan2(end.y - start.y, end.x - start.x)
scope.drawPath(
Path().apply {
fillType = PathFillType.EvenOdd
moveTo(end.x, end.y)
lineTo(
(end.x - radius * cos(lineAngle - (angleInRad / 2))).toFloat(),
(end.y - radius * sin(lineAngle - (angleInRad / 2))).toFloat()
)
moveTo(end.x, end.y)
lineTo(
(end.x - radius * cos(lineAngle + (angleInRad / 2))).toFloat(),
(end.y - radius * sin(lineAngle + (angleInRad / 2))).toFloat()
)
},
color,
style = stroke
)
}
}

class Rectangle(properties: DrawableProperties) : Drawable(properties) {

override fun draw(scope: DrawScope) {
scope.drawRect(color, start, (end - start).size(), style = stroke)
}
}

class Oval(properties: DrawableProperties) : Drawable(properties) {

override fun draw(scope: DrawScope) {
scope.drawOval(color, start, (end - start).size(), style = stroke)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.aritra.notify.components.drawing

import android.graphics.Canvas
import android.view.MotionEvent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.input.pointer.pointerInteropFilter
import com.aritra.notify.utils.absoluteValue
import com.aritra.notify.utils.pos

typealias DrawableFactory = (DrawableProperties) -> Drawable
typealias ColorFactory = () -> Color

private const val TOUCH_TOLERANCE = 4
private val stroke = Stroke(
width = 10f,
cap = StrokeCap.Round,
join = StrokeJoin.Round
)

/**
* The current offset of the finger
*/
private var currentOffset = 0 pos 0

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun DrawingCanvas(
modifier: Modifier = Modifier,
drawableFactory: DrawableFactory,
colorFactory: ColorFactory,
) {
val drawables = remember { mutableListOf<Drawable>() }
var invalidate by remember { mutableIntStateOf(0) }
var canvas by remember { mutableStateOf<Canvas?>(null) }

LaunchedEffect(Unit) {
// reset the current offset when the composable is first launched
currentOffset = 0 pos 0
}

val touchStart: (Offset) -> Unit = remember(drawableFactory, colorFactory) {
{ offset ->
// save the current coordinates of the finger
currentOffset = offset
// create a new drawable and add it to the list
drawables += drawableFactory(DrawableProperties(offset, offset, stroke, colorFactory()))
}
}

val touchMove: (Offset) -> Unit = remember {
{ offset ->
val change = (offset - currentOffset).absoluteValue()

if (change.x >= TOUCH_TOLERANCE || change.y >= TOUCH_TOLERANCE) {
currentOffset = offset
drawables.lastOrNull()?.end = offset
}
}
}

Canvas(
modifier = modifier
.fillMaxSize()
.background(Color.White)
.pointerInteropFilter { event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
touchStart(event.x pos event.y)
invalidate++
true
}

MotionEvent.ACTION_MOVE -> {
touchMove(event.x pos event.y)
invalidate++
true
}

MotionEvent.ACTION_UP -> {
invalidate++
true
}

else -> false
}
},
onDraw = {
if (canvas == null) {
canvas = drawContext.canvas.nativeCanvas
}
// invalidate is called whenever a new stroke is added so the canvas is redrawn
invalidate.let {
// iterate over each stroke and draw it on the canvas
drawables.forEach { it.draw(this) }
}
}
)
}
Loading

0 comments on commit 3e3ab57

Please sign in to comment.