diff --git a/.idea/gradle.xml b/.idea/gradle.xml index caf821d..e2cf8ff 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,5 +1,6 @@ + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/androidTest/java/eu/wewox/pagecurl/ExampleInstrumentedTest.kt b/app/src/androidTest/java/eu/wewox/pagecurl/ExampleInstrumentedTest.kt deleted file mode 100644 index 690f5d8..0000000 --- a/app/src/androidTest/java/eu/wewox/pagecurl/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.wewox.pagecurl - -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("eu.wewox.pagecurl", appContext.packageName) - } -} diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..5365c18 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/eu/wewox/pagecurl/page/CurlDraw.kt b/app/src/main/java/eu/wewox/pagecurl/page/CurlDraw.kt new file mode 100644 index 0000000..1385369 --- /dev/null +++ b/app/src/main/java/eu/wewox/pagecurl/page/CurlDraw.kt @@ -0,0 +1,79 @@ +package eu.wewox.pagecurl.page + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.drawscope.rotateRad +import androidx.compose.ui.graphics.drawscope.withTransform +import java.lang.Float.max +import kotlin.math.atan2 + +fun Modifier.drawCurl( + posA: Offset?, + posB: Offset?, +): Modifier = drawWithContent { + if (posA == null || posB == null) { + drawContent() + return@drawWithContent + } + + val topIntersection = lineLineIntersection( + Offset(0f, 0f), Offset(size.width, 0f), + posA, posB + ) + val bottomIntersection = lineLineIntersection( + Offset(0f, size.height), Offset(size.width, size.height), + posA, posB + ) + if (topIntersection == null || bottomIntersection == null) { + drawContent() + return@drawWithContent + } + + val topCurlOffset = Offset(max(0f, topIntersection.x), topIntersection.y) + val bottomCurlOffset = Offset(max(0f, bottomIntersection.x), bottomIntersection.y) + + val lineVector = topCurlOffset - bottomCurlOffset + + val path = Path() + path.lineTo(topCurlOffset.x, topCurlOffset.y) + path.lineTo(bottomCurlOffset.x, bottomCurlOffset.y) + path.lineTo(0f, size.height) + clipPath(path) { this@drawWithContent.drawContent() } + + withTransform({ + scale(-1f, 1f, pivot = bottomCurlOffset) + + val angle = atan2(-lineVector.y, lineVector.x) + rotateRad(Math.PI.toFloat() + 2 * angle, pivot = bottomCurlOffset) + + val path2 = Path() + path2.moveTo(topCurlOffset.x, topCurlOffset.y) + path2.lineTo(max(size.width, topCurlOffset.x), topCurlOffset.y) + path2.lineTo(max(size.width, bottomCurlOffset.x), bottomCurlOffset.y) + path2.lineTo(bottomCurlOffset.x, bottomCurlOffset.y) + clipPath(path2) + }) { + this@drawWithContent.drawContent() + drawRect(Color.White.copy(alpha = 0.8f)) + } +} + +private fun lineLineIntersection( + line1a: Offset, + line1b: Offset, + line2a: Offset, + line2b: Offset, +): Offset? { + val denominator = (line1a.x - line1b.x) * (line2a.y - line2b.y) - (line1a.y - line1b.y) * (line2a.x - line2b.x) + if (denominator == 0f) return null + + val x = ((line1a.x * line1b.y - line1a.y * line1b.x) * (line2a.x - line2b.x) - + (line1a.x - line1b.x) * (line2a.x * line2b.y - line2a.y * line2b.x)) / denominator + val y = ((line1a.x * line1b.y - line1a.y * line1b.x) * (line2a.y - line2b.y) - + (line1a.y - line1b.y) * (line2a.x * line2b.y - line2a.y * line2b.x)) / denominator + return Offset(x, y) +} diff --git a/app/src/main/java/eu/wewox/pagecurl/page/CurlGesture.kt b/app/src/main/java/eu/wewox/pagecurl/page/CurlGesture.kt new file mode 100644 index 0000000..47cfd1d --- /dev/null +++ b/app/src/main/java/eu/wewox/pagecurl/page/CurlGesture.kt @@ -0,0 +1,29 @@ +package eu.wewox.pagecurl.page + +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.forEachGesture +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChangeConsumed + +fun Modifier.curlGesture( + onCurl: (Offset, Offset) -> Unit, + onCancel: () -> Unit, +): Modifier = pointerInput(true) { + forEachGesture { + awaitPointerEventScope { + awaitFirstDown(requireUnconsumed = false) + do { + val event = awaitPointerEvent() + val canceled = event.changes.any { it.positionChangeConsumed() } + val posA = event.changes.getOrNull(0)?.position + val posB = event.changes.getOrNull(1)?.position + if (posA != null && posB != null) { + onCurl(posA, posB) + } + } while (!canceled && event.changes.any { it.pressed }) + onCancel() + } + } +} diff --git a/app/src/main/java/eu/wewox/pagecurl/page/Page.kt b/app/src/main/java/eu/wewox/pagecurl/page/Page.kt index 76b43b7..de637fd 100644 --- a/app/src/main/java/eu/wewox/pagecurl/page/Page.kt +++ b/app/src/main/java/eu/wewox/pagecurl/page/Page.kt @@ -1,11 +1,6 @@ package eu.wewox.pagecurl.page -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -13,17 +8,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.clipPath -import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import eu.wewox.pagecurl.R import eu.wewox.pagecurl.utils.Data @Composable @@ -37,80 +32,37 @@ fun Page() { .padding(16.dp) ) - val infiniteTransition = rememberInfiniteTransition() - val progress by infiniteTransition.animateFloat( - initialValue = 0.05f, - targetValue = 0.95f, - animationSpec = infiniteRepeatable( - animation = tween(2000, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse - ) - ) + var posA by remember { mutableStateOf(null) } + var posB by remember { mutableStateOf(null) } + Box( Modifier - .drawWithContent { - val curlWidth = size.width * progress - - val midX = size.width - curlWidth - val path = Path() - path.lineTo(midX, 0f) - path.lineTo(midX, size.height) - path.lineTo(0f, size.height) - clipPath(path) { this@drawWithContent.drawContent() } - - drawLine( - Brush.horizontalGradient( - listOf(Color.Black.copy(alpha = 0.5f), Color.Black.copy(alpha = 0f)), - midX - curlWidth * 0.8f + 40f, - midX - curlWidth * 0.8f - 40f - ), - Offset(midX - curlWidth * 0.8f, size.height), - Offset(midX - curlWidth * 0.8f, 0f), - 80f - ) - - drawLine( - Brush.horizontalGradient( - listOf(Color.Black.copy(alpha = 0.5f), Color.Black.copy(alpha = 0f)), - midX - 30f, - midX + 30f - ), - Offset(midX, size.height), - Offset(midX, 0f), - 60f - ) - - drawRect( - Color.White, - topLeft = Offset(midX - curlWidth * 0.8f, 0f), - size = Size(curlWidth * 0.8f, size.height) - ) - - - withTransform({ - scale(-1f, 1f) - clipRect(size.width - midX + curlWidth * 0.8f, 0f, size.width - midX, size.height) - translate(-midX + curlWidth * 0.8f) - }) { - this@drawWithContent.drawContent() - drawRect(Color.White.copy(alpha = 0.8f)) + .curlGesture( + onCurl = { a, b -> + posA = a + posB = b + }, + onCancel = { + posA = null + posB = null } - } + ) + .drawCurl(posA, posB) ) { - Text( - text = Data.Lorem1, - fontSize = 22.sp, - modifier = Modifier - .fillMaxSize() - .background(Color.White) - .padding(16.dp) - ) - // Image( - // painter = painterResource(R.drawable.img_sleep), - // contentDescription = null, - // contentScale = ContentScale.Crop, + // Text( + // text = Data.Lorem1, + // fontSize = 22.sp, // modifier = Modifier // .fillMaxSize() + // .background(Color.White) + // .padding(16.dp) // ) + Image( + painter = painterResource(R.drawable.img_sleep), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + ) } } diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 7706ab9..0000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..bdb4710 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6b78462..a8a8fa5 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6b78462..a8a8fa5 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..9d122de Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..5a6419f Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..b30fe46 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..1f1438d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..930ae89 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..18f747a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b20ad3e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7af5162 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..9e8234b Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e43dd06 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/test/java/eu/wewox/pagecurl/ExampleUnitTest.kt b/app/src/test/java/eu/wewox/pagecurl/ExampleUnitTest.kt deleted file mode 100644 index 3bd6f3b..0000000 --- a/app/src/test/java/eu/wewox/pagecurl/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.wewox.pagecurl - -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) - } -}