diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 526b4c2..d7f5619 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/build.gradle b/app/build.gradle
index 0ad8f9d..70251be 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -49,6 +49,8 @@ android {
dependencies {
+ implementation project(":scratchview")
+
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
diff --git a/app/src/main/java/com/vivek/scratchcardeffect/ui/screens/ScratchCardScreen.kt b/app/src/main/java/com/vivek/scratchcardeffect/ui/screens/ScratchCardScreen.kt
index 256f5cf..1836bf9 100644
--- a/app/src/main/java/com/vivek/scratchcardeffect/ui/screens/ScratchCardScreen.kt
+++ b/app/src/main/java/com/vivek/scratchcardeffect/ui/screens/ScratchCardScreen.kt
@@ -1,15 +1,16 @@
package com.vivek.scratchcardeffect.ui.screens
-import android.view.MotionEvent
-import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.runtime.Composable
@@ -19,20 +20,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.drawscope.clipPath
-import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.res.imageResource
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.vivek.scratchcardeffect.R
import com.vivek.scratchcardeffect.models.DraggedPath
+import com.vivek.scratchview.ScratchingCanvas
@ExperimentalComposeUiApi
@Composable
@@ -64,69 +60,24 @@ fun ScratchCardScreen() {
// Scratch Card Implementation
ScratchingCanvas(
overlayImage = overlayImage,
- baseImage = baseImage,
- modifier = Modifier.align(Alignment.Center),
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(200.dp)
+ .clip(RoundedCornerShape(size = 16.dp)),
movedOffset = movedOffsetState.value,
onMovedOffset = { x, y ->
movedOffsetState.value = Offset(x, y)
},
currentPath = currentPathState.value.path,
currentPathThickness = currentPathState.value.width,
- )
- }
-}
-
-@ExperimentalComposeUiApi
-@Composable
-fun ScratchingCanvas(
- overlayImage: ImageBitmap,
- baseImage: ImageBitmap,
- modifier: Modifier = Modifier,
- movedOffset: Offset?,
- onMovedOffset: (Float, Float) -> Unit,
- currentPath: Path,
- currentPathThickness: Float,
-) {
- Canvas(
- modifier = modifier
- .size(220.dp)
- .clipToBounds()
- .clip(RoundedCornerShape(size = 16.dp))
- .pointerInteropFilter {
- when (it.action) {
- MotionEvent.ACTION_DOWN -> {
- println("CurrentPath/ACTION_DOWN: (${it.x}, ${it.y})")
- currentPath.moveTo(it.x, it.y)
- }
- MotionEvent.ACTION_MOVE -> {
- println("MovedOffset/ACTION_MOVE: (${it.x}, ${it.y})")
- onMovedOffset(it.x, it.y)
- }
- }
- true
+ background = {
+ Image(bitmap = baseImage, contentDescription = "", modifier = Modifier.size(220.dp))
+ /* Column(modifier = Modifier.align(Alignment.Center)) {
+ Text(text = "Hello")
+ Text(text = "Android")
+ }*/
}
- ) {
- val canvasWidth = size.width.toInt()
- val canvasHeight = size.height.toInt()
- val imageSize = IntSize(width = canvasWidth, height = canvasHeight)
-
- // Overlay Image to be scratched
- drawImage(
- image = overlayImage,
- dstSize = imageSize
)
-
- movedOffset?.let {
- currentPath.addOval(oval = Rect(it, currentPathThickness))
- }
-
- clipPath(path = currentPath, clipOp = ClipOp.Intersect) {
- // Base Image after scratching
- drawImage(
- image = baseImage,
- dstSize = imageSize
- )
- }
}
}
diff --git a/app/src/main/java/com/vivek/scratchcardeffect/utils/Utils.kt b/app/src/main/java/com/vivek/scratchcardeffect/utils/Utils.kt
new file mode 100644
index 0000000..2f7707a
--- /dev/null
+++ b/app/src/main/java/com/vivek/scratchcardeffect/utils/Utils.kt
@@ -0,0 +1,16 @@
+package com.vivek.scratchcardeffect.utils
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.view.View
+
+fun View.takeScreenShot() : Bitmap {
+ val bitmap = Bitmap.createBitmap(
+ this.width,
+ this.height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(bitmap)
+ this.draw(canvas)
+ return bitmap
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index f5a1307..a2f0576 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21"
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/scratchview/.gitignore b/scratchview/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/scratchview/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/scratchview/build.gradle b/scratchview/build.gradle
new file mode 100644
index 0000000..816bfc4
--- /dev/null
+++ b/scratchview/build.gradle
@@ -0,0 +1,54 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 31
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ useIR = true
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerVersion '1.5.21'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'com.google.android.material:material:1.4.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ implementation "androidx.compose.ui:ui:$compose_version"
+ implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
+ implementation 'androidx.activity:activity-compose:1.3.1'
+}
\ No newline at end of file
diff --git a/scratchview/consumer-rules.pro b/scratchview/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/scratchview/proguard-rules.pro b/scratchview/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/scratchview/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/scratchview/src/androidTest/java/com/vivek/scratchview/ExampleInstrumentedTest.kt b/scratchview/src/androidTest/java/com/vivek/scratchview/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..822da70
--- /dev/null
+++ b/scratchview/src/androidTest/java/com/vivek/scratchview/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.vivek.scratchview
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.vivek.scratchview.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/scratchview/src/main/AndroidManifest.xml b/scratchview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f754875
--- /dev/null
+++ b/scratchview/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/scratchview/src/main/java/com/vivek/scratchview/Capturable.kt b/scratchview/src/main/java/com/vivek/scratchview/Capturable.kt
new file mode 100644
index 0000000..c0f22ee
--- /dev/null
+++ b/scratchview/src/main/java/com/vivek/scratchview/Capturable.kt
@@ -0,0 +1,50 @@
+package com.vivek.scratchview
+
+import android.graphics.Bitmap
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.runtime.*
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.drawToBitmap
+
+/**
+ * Adds ability to capture the [Composable] component in the form of [Bitmap]
+ *
+ * @param captureRequestKey Unique key for capture request
+ * @param onBitmapCaptured Callback providing captured [Bitmap] of a [content]
+ * @param content Composable content to be captured
+ */
+@Composable
+fun Capturable(
+ captureRequestKey: Any? = null,
+ onBitmapCaptured: (Bitmap) -> Unit,
+ content: @Composable () -> Unit
+) {
+ val latestCapturedCallback by rememberUpdatedState(onBitmapCaptured)
+
+ val context = LocalContext.current
+ val view = remember { ComposeView(context) }
+
+ AndroidView(
+ factory = {
+ view.apply {
+ setContent {
+ Surface(color = MaterialTheme.colors.background) {
+ content()
+ }
+ }
+ }
+ }
+ )
+
+ // If key is changed it means it's requested to capture a Bitmap
+ LaunchedEffect(captureRequestKey) {
+ if (captureRequestKey != null) {
+ view.post {
+ latestCapturedCallback(view.drawToBitmap())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scratchview/src/main/java/com/vivek/scratchview/ScratchView.kt b/scratchview/src/main/java/com/vivek/scratchview/ScratchView.kt
new file mode 100644
index 0000000..b4cf68b
--- /dev/null
+++ b/scratchview/src/main/java/com/vivek/scratchview/ScratchView.kt
@@ -0,0 +1,74 @@
+package com.vivek.scratchview
+
+import android.graphics.Bitmap
+import android.view.MotionEvent
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.IntSize
+
+@ExperimentalComposeUiApi
+@Composable
+fun ScratchingCanvas(
+ overlayImage: ImageBitmap,
+ modifier: Modifier = Modifier,
+ movedOffset: Offset?,
+ onMovedOffset: (Float, Float) -> Unit,
+ currentPath: Path,
+ currentPathThickness: Float,
+ background: @Composable () -> Unit
+) {
+
+ Box(modifier = modifier.clipToBounds()) {
+ // Base Image
+ background()
+
+ // Overlay Image
+ Canvas(
+ modifier = Modifier
+ .fillMaxSize()
+ .pointerInteropFilter {
+ when (it.action) {
+ MotionEvent.ACTION_DOWN -> {
+ println("CurrentPath/ACTION_DOWN: (${it.x}, ${it.y})")
+ currentPath.moveTo(it.x, it.y)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ println("MovedOffset/ACTION_MOVE: (${it.x}, ${it.y})")
+ onMovedOffset(it.x, it.y)
+ }
+ }
+ true
+ }
+ ) {
+ val canvasWidth = size.width.toInt()
+ val canvasHeight = size.height.toInt()
+ val imageSize = IntSize(width = canvasWidth, height = canvasHeight)
+
+ movedOffset?.let {
+ currentPath.addOval(oval = Rect(it, currentPathThickness))
+ }
+
+ clipPath(path = currentPath, clipOp = ClipOp.Difference) {
+ // Base Image after scratching
+ drawImage(
+ image = overlayImage,
+ dstSize = imageSize
+ )
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/scratchview/src/test/java/com/vivek/scratchview/ExampleUnitTest.kt b/scratchview/src/test/java/com/vivek/scratchview/ExampleUnitTest.kt
new file mode 100644
index 0000000..8cea251
--- /dev/null
+++ b/scratchview/src/test/java/com/vivek/scratchview/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.vivek.scratchview
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 3bd6659..49ffafd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,3 +8,4 @@ dependencyResolutionManagement {
}
rootProject.name = "ScratchCardEffect"
include ':app'
+include ':scratchview'