diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..5613b39 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,32 @@ +name: Android Debug Build + +on: + workflow_dispatch: + +jobs: + build: + name: Build Debug APK + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 # optional upgrade, recommended + + - name: Set up JDK + uses: actions/setup-java@v4 # optional upgrade, recommended + with: + distribution: temurin + java-version: 17 + + - name: Make Gradle executable + run: chmod +x ./gradlew + + - name: Build Debug APK + run: ./gradlew assembleDebug --stacktrace + + - name: Upload Debug APK + uses: actions/upload-artifact@v4 # yahan v3 se v4 + with: + name: debug-apk + path: app/build/outputs/apk/debug/*.apk + if-no-files-found: error # optional, par helpful diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawController.kt b/app/src/main/java/com/shezik/drawanywhere/DrawController.kt index 9e53eb3..44dd8ae 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawController.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawController.kt @@ -29,7 +29,9 @@ import kotlinx.coroutines.flow.asStateFlow import java.util.UUID enum class PenType { - Pen, StrokeEraser, /*PixelEraser*/ // TODO + Pen, + Rectangle, + StrokeEraser, /*PixelEraser*/ // TODO } data class PenConfig( @@ -46,17 +48,23 @@ data class PathWrapper( private var cachedPathInvalid: MutableState = mutableStateOf(true), val color: Color, val width: Float, - val alpha: Float + val alpha: Float, + val smooth: Boolean = true ) { - val cachedPath: Path get() = - if ((_cachedPath.value == null) or cachedPathInvalid.value) - rebuildPath().value - else - _cachedPath.value!! + val cachedPath: Path + get() = + if ((_cachedPath.value == null) || cachedPathInvalid.value) + rebuildPath().value + else + _cachedPath.value!! @Suppress("UNCHECKED_CAST") - private fun rebuildPath(): MutableState { // TODO: Find a way to append points to the cached path instead of complete recalculation - _cachedPath.value = pointsToPath(points) + private fun rebuildPath(): MutableState { + _cachedPath.value = if (smooth) { + pointsToPath(points) + } else { + pointsToPolyline(points) + } cachedPathInvalid.value = false return _cachedPath as MutableState } @@ -109,7 +117,26 @@ class DrawController { } _pathList.lastOrNull()?.let { latestPath -> - latestPath.points.add(newPoint) + if (penConfig.penType == PenType.Rectangle) { + // Rectangle: build polygon from start point and current point + val start = latestPath.points.firstOrNull() ?: return + + val sx = start.x + val sy = start.y + val ex = newPoint.x + val ey = newPoint.y + + latestPath.points.clear() + latestPath.points.add(start) // top-left + latestPath.points.add(Offset(ex, sy)) // top-right + latestPath.points.add(Offset(ex, ey)) // bottom-right + latestPath.points.add(Offset(sx, ey)) // bottom-left + latestPath.points.add(start) // close polygon + } else { + // Freehand pen + latestPath.points.add(newPoint) + } + latestPath.invalidatePath() } } @@ -123,12 +150,15 @@ class DrawController { return } - _pathList.add(PathWrapper( - points = mutableStateListOf(newPoint), - color = penConfig.color, - width = penConfig.width, - alpha = penConfig.alpha - )) + _pathList.add( + PathWrapper( + points = mutableStateListOf(newPoint), + color = penConfig.color, + width = penConfig.width, + alpha = penConfig.alpha, + smooth = penConfig.penType != PenType.Rectangle + ) + ) } fun finishPath() { @@ -143,7 +173,8 @@ class DrawController { } redoStack.clear() - addToUndoStack(DrawAction.AddPath(latestPath)) // Shallow copy, we aren't touching its cachedPath. Undo/redo methods below depend on shallow copying. + // Shallow copy, we aren't touching its cachedPath. Undo/redo methods below depend on shallow copying. + addToUndoStack(DrawAction.AddPath(latestPath)) updateUndoRedoState() updateClearPathsState() } diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt b/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt index a5673e4..add5f76 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawToolbar.kt @@ -40,6 +40,7 @@ import androidx.compose.material.icons.automirrored.filled.Redo import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.CropSquare import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -582,7 +583,8 @@ private fun PenTypeSelector( ) val penTypes = listOf( - PenType.Pen to stringResource(R.string.pen), + PenType.Rectangle to stringResource(R.string.rectangle), + PenType.Pen to stringResource(R.string.pen), PenType.StrokeEraser to stringResource(R.string.stroke_eraser) ) @@ -994,23 +996,25 @@ private fun createAllToolbarButtons( ToolbarButton( id = "tool_controls", icon = when (uiState.currentPenType) { - PenType.Pen -> Icons.Default.Edit + PenType.Rectangle -> Icons.Outlined.CropSquare + PenType.Pen -> Icons.Default.Edit PenType.StrokeEraser -> InkEraser24Px }, contentDescription = stringResource(R.string.tool_controls), popupPages = listOf( - { PenTypeSelector( - currentPenType = uiState.currentPenType, - onPenTypeSwitch = onPenTypeSwitch - ) }, - - { PenControls( - penConfig = uiState.currentPenConfig, - onStrokeWidthChange = onStrokeWidthChange, - onAlphaChange = onAlphaChange - ) } - ) - ), + { + PenTypeSelector( + currentPenType = uiState.currentPenType, + onPenTypeSwitch = onPenTypeSwitch + ) }, + { + PenControls( + penConfig = uiState.currentPenConfig, + onStrokeWidthChange = onStrokeWidthChange, + onAlphaChange = onAlphaChange + ) } + ) + ), ToolbarButton( id = "color_picker", diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt b/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt index 2d26ae5..de046e6 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawUtils.kt @@ -72,4 +72,14 @@ fun pointsToPath(points: List) = Path().apply { quadraticTo(start.x, start.y, mid.x, mid.y) } lineTo(points.last().x, points.last().y) +} + +fun pointsToPolyline(points: List) = Path().apply { + if (points.isEmpty()) + return@apply + + moveTo(points.first().x, points.first().y) + points.drop(1).forEach { point -> + lineTo(point.x, point.y) + } } \ No newline at end of file diff --git a/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt b/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt index 8659721..7c67b78 100644 --- a/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt +++ b/app/src/main/java/com/shezik/drawanywhere/DrawViewModel.kt @@ -71,6 +71,7 @@ data class UiState( fun defaultPenConfigs(): Map = mapOf( PenType.Pen to PenConfig(penType = PenType.Pen), + PenType.Rectangle to PenConfig(penType = PenType.Rectangle), PenType.StrokeEraser to PenConfig(penType = PenType.StrokeEraser, width = 50f) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index efd4e05..013b022 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Tools Pen Stroke Eraser + Rectangle Color Width Opacity