feat: support compose multiplatform

This commit is contained in:
2023-10-31 03:07:47 +08:00
parent 0eb979c257
commit 2feee4c266
115 changed files with 1362 additions and 1729 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# noinspection EditorConfigKeyCorrectness
[{*.kt,*.kts}]
ktlint_standard_annotation = disabled
ktlint_standard_filename = disabled
ktlint_standard_wrapping = disabled
ktlint_standard_import-ordering = enabled
ktlint_standard_max-line-length = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_parameter-list-wrapping = disabled
ktlint_standard_trailing-comma-on-declaration-site = disabled
ktlint_function_signature_body_expression_wrapping = multiline
ij_continuation_indent_size = 2
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 150

View File

@@ -1,40 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Publish To Maven Central
# Controls when the workflow will run
on:
# Triggers the workflow when tag is pushed
push:
tags:
- 'v*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "publish"
publish:
# The type of runner that the job will run on
runs-on: macos-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Runs a single command using the runners shell
- name: publish
run: ./gradlew publish --no-daemon --no-parallel
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}

View File

@@ -1,47 +0,0 @@
# This is a workflow to verify PRs with static code analysis tools
name: Static Analysis
# Controls when the workflow will run
on:
pull_request:
branches: [main]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
detekt:
name: Detekt
runs-on: macos-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Runs a single command using the runners shell
- name: detekt
run: ./gradlew detekt
spotless:
name: Spotless
runs-on: macos-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Runs a single command using the runners shell
- name: spotless
run: ./gradlew spotlessCheck

10
.gitignore vendored
View File

@@ -1,9 +1,15 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea /.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store .DS_Store
build/ /build
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties

4
.idea/.gitignore generated vendored
View File

@@ -1,3 +1,5 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /gradle.xml
/misc.xml
/workspace.xml

26
.idea/appInsightsSettings.xml generated Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@@ -1,205 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="RIGHT_MARGIN" value="120" />
<HTMLCodeStyleSettings>
<option name="HTML_TEXT_WRAP" value="0" />
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSON>
<option name="ARRAY_WRAPPING" value="5" />
</JSON>
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="m" />
<option name="STATIC_FIELD_NAME_PREFIX" value="s" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
<option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>
<option name="XML_ATTRIBUTE_WRAP" value="4" />
</XML>
<editorconfig>
<option name="ENABLED" value="false" />
</editorconfig>
<codeStyleSettings language="Groovy">
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
</codeStyleSettings>
<codeStyleSettings language="JSON">
<option name="KEEP_LINE_BREAKS" value="false" />
<indentOptions>
<option name="INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="0" />
<option name="METHOD_PARAMETERS_WRAP" value="0" />
<option name="EXTENDS_LIST_WRAP" value="0" />
<option name="METHOD_CALL_CHAIN_WRAP" value="0" />
<option name="ASSIGNMENT_WRAP" value="0" />
<option name="VARIABLE_ANNOTATION_WRAP" value="2" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

10
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="androidApp">
<State />
</entry>
</value>
</component>
</project>

35
.idea/gradle.xml generated
View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT">
<builds>
<build path="$PROJECT_DIR$/build-logic" name="build-logic">
<projects>
<project path="$PROJECT_DIR$/build-logic" />
<project path="$PROJECT_DIR$/build-logic/convention" />
</projects>
</build>
</builds>
</compositeBuild>
</compositeConfiguration>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/local/Cellar/gradle/6.0.1/libexec" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/build-logic" />
<option value="$PROJECT_DIR$/build-logic/convention" />
<option value="$PROJECT_DIR$/demo" />
<option value="$PROJECT_DIR$/pagecurl" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.10" />
</component>
</project>

6
.idea/ktlint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtlintProjectConfiguration">
<treatAsErrors>false</treatAsErrors>
</component>
</project>

16
.idea/misc.xml generated
View File

@@ -1,16 +0,0 @@
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../../layout/compose-model-1647938366739.xml" value="0.34881756756756754" />
<entry key="app/src/main/res/drawable/ic_launcher_foreground.xml" value="0.247" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

2
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>

137
README.md
View File

@@ -1,10 +1,18 @@
[![Maven Central](https://img.shields.io/maven-central/v/io.github.oleksandrbalan/pagecurl.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.github.oleksandrbalan/pagecurl) [![Maven Central](https://img.shields.io/maven-central/v/io.github.oleksandrbalan/pagecurl.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.github.oleksandrbalan/pagecurl)
<img align="right" src="https://user-images.githubusercontent.com/20944869/200791917-a2436c9a-d062-4c14-9c71-c94fe8703061.png"> <img alt="LOGO" align="right" src="https://user-images.githubusercontent.com/20944869/200791917-a2436c9a-d062-4c14-9c71-c94fe8703061.png">
# Page Curl # Page Curl (Multiplatform)
Page Curl library for Jetpack Compose. Page Curl library for Jetpack Compose Multiplatform.
Support for Android, iOS, Desktop (JVM):
<img width="1614" alt="SHOT" src="https://github.com/fankes/pagecurl-multiplatform/assets/37344460/48bb2aa7-12a5-43c5-aa9f-028e83a06746">
> Note
This is just an attempt at multiplatform porting, and there may still be some problems.
## Motivation ## Motivation
@@ -12,122 +20,19 @@ This library allows to create an effect of turning pages, which can be used in b
## Usage ## Usage
### Get a dependency > Note
**Step 1.** Add the MavenCentral repository to your build file. This multiplatform library's artifact is not upload to any repositories,
Add it in your root `build.gradle.kts` at the end of repositories: you can clone this repository or use git submodule to use it.
```kotlin
allprojects {
repositories {
...
mavenCentral()
}
}
```
Or in `settings.gradle.kts`: For an Android version and a sample usage, please visit: https://github.com/oleksandrbalan/pagecurl.
```kotlin
pluginManagement {
repositories {
...
mavenCentral()
}
}
```
**Step 2.** Add the dependency. See Demo application
Check latest version on the [releases page](https://github.com/oleksandrbalan/pagecurl/releases). and [examples](https://github.com/fankes/pagecurl/blob/multiplatform/samples/shared/src/commonMain/kotlin/eu/wewox/pagecurl/screens) for more usage
```kotlin examples.
dependencies {
implementation("io.github.oleksandrbalan:pagecurl:$version")
}
```
### Use in Composable ## Thanks
The `PageCurl` has 2 mandatory arguments: [The compose multiplatform shadows way](https://github.com/oleksandrbalan/pagecurl/issues/23#issuecomment-1767145310)
* **count** - The count of pages.
* **content** - The content lambda to provide the page composable. Receives the page number.
``` [Multiplatform Paging](https://github.com/cashapp/multiplatform-paging)
val pages = listOf("One", "Two", "Three")
PageCurl(count = pages.size) { index ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
Text(
text = pages[index],
style = MaterialTheme.typography.h1,
)
}
}
```
Optionally `state` could be provided to observe and manage PageCurl state.
* **state** - The state of the PageCurl. Use it to programmatically change the current page or observe changes, and to configure shadow, back-page and interactions.
```
Column {
val scope = rememberCoroutineScope()
val state = rememberPageCurlState()
Button(onClick = { scope.launch { state.next() } }) {
Text(text = "Next")
}
val pages = listOf("One", "Two", "Three")
PageCurl(
count = pages.size,
state = state,
) { index ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
Text(
text = pages[index],
style = MaterialTheme.typography.h1,
)
}
}
}
```
Optionally `key` lambda could be provided with stable key for each item. PageCurl with keys for each page will correctly preserve a current position when items are added or removed.
* **key** - The lambda to provide stable key for each item. Useful when adding and removing items before current page.
```
Column {
var pages by remember { mutableStateOf(listOf("Four", "Five", "Six")) }
Button(onClick = { pages = listOf("One", "Two", "Three") + pages }) {
Text(text = "Prepend new pages")
}
PageCurl(
count = pages.size,
key = { pages[it].hashCode() },
) { index ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize()
) {
Text(
text = pages[index],
style = MaterialTheme.typography.h1,
)
}
}
}
```
See Demo application and [examples](demo/src/main/kotlin/eu/wewox/pagecurl/screens) for more usage examples.
https://user-images.githubusercontent.com/20944869/185782671-2861c2ed-c033-4318-bf12-1d8db74fc8b5.mp4
https://user-images.githubusercontent.com/20944869/185782668-b52da2b9-be8d-49db-8729-88b6f9a8ee48.mp4
https://user-images.githubusercontent.com/20944869/185782663-4bd97a57-1a46-408d-a07b-34c193f01aba.mp4

View File

@@ -1,19 +0,0 @@
plugins {
`kotlin-dsl`
}
java {
toolchain {
val version = libs.versions.java.toolchain.get()
languageVersion.set(JavaLanguageVersion.of(version))
}
}
gradlePlugin {
plugins {
register("conventionJvmToolchain") {
id = "convention.jvm.toolchain"
implementationClass = "JvmToolchainConventionPlugin"
}
}
}

View File

@@ -1,18 +0,0 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.configure
import utils.libs
class JvmToolchainConventionPlugin : Plugin<Project> {
override fun apply(target: Project) =
with(target) {
extensions.configure<JavaPluginExtension> {
toolchain {
val version = libs.findVersion("java-toolchain").get().displayName
languageVersion.set(JavaLanguageVersion.of(version))
}
}
}
}

View File

@@ -1,9 +0,0 @@
package utils
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
val Project.libs: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")

View File

@@ -1,18 +0,0 @@
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version ("0.7.0")
}
rootProject.name = "build-logic"
include(":convention")

View File

@@ -1,23 +1,6 @@
import com.diffplug.gradle.spotless.SpotlessPlugin
import io.gitlab.arturbosch.detekt.DetektPlugin
plugins { plugins {
alias(libs.plugins.android.application) apply false autowire(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.android.library) apply false autowire(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin) apply false autowire(libs.plugins.android.library) apply false
alias(libs.plugins.detekt) autowire(libs.plugins.jetbrains.compose) apply false
alias(libs.plugins.spotless) }
alias(libs.plugins.mavenpublish)
}
configure(subprojects) {
apply<DetektPlugin>()
apply<SpotlessPlugin>()
spotless {
kotlin {
target("**/*.kt")
ktlint("0.43.2")
}
}
}

View File

@@ -1,684 +0,0 @@
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors:
active: true
exclude:
- 'DetektProgressListener'
# - 'KtFileCountProcessor'
# - 'PackageCountProcessor'
# - 'ClassCountProcessor'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ProjectComplexityProcessor'
# - 'ProjectCognitiveComplexityProcessor'
# - 'ProjectLLOCProcessor'
# - 'ProjectCLOCProcessor'
# - 'ProjectLOCProcessor'
# - 'ProjectSLOCProcessor'
# - 'LicenseHeaderLoaderExtension'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FindingsReport'
- 'FileBasedFindingsReport'
# - 'LiteFindingsReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
comments:
active: true
AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
OutdatedDocumentation:
active: false
matchTypeParameters: true
matchDeclarationsOrder: true
allowParamOnConstructorProperties: false
UndocumentedPublicClass:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UndocumentedPublicProperty:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: []
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
functionThreshold: 10
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotated: [ 'Composable' ]
ignoreAnnotatedParameter: []
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
ignoreArgumentsMatchingNames: false
NestedBlockDepth:
active: true
threshold: 4
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: false
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: false
SuspendFunWithCoroutineScopeReceiver:
active: false
SuspendFunWithFlowReturnType:
active: false
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
naming:
active: true
BooleanPropertyNaming:
active: false
allowedPattern: '^(is|has|are)'
ignoreOverridden: true
ClassNaming:
active: true
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: []
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
ignoreAnnotated: [ 'Composable' ]
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
rootPackage: ''
requireRootInDeclaration: false
LambdaParameterNaming:
active: false
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NoNameShadowing:
active: false
NonBooleanPropertyPrefixedWithIs:
active: false
ObjectPropertyNaming:
active: true
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
maximumVariableNameLength: 64
VariableMinLength:
active: false
minimumVariableNameLength: 1
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: true
ForEachOnRange:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
SpreadOperator:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: false
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
active: false
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: false
mutableTypes:
- 'kotlin.collections.MutableList'
- 'kotlin.collections.MutableMap'
- 'kotlin.collections.MutableSet'
- 'java.util.ArrayList'
- 'java.util.LinkedHashSet'
- 'java.util.HashSet'
- 'java.util.LinkedHashMap'
- 'java.util.HashMap'
DuplicateCaseInWhenExpression:
active: true
ElseCaseInsteadOfExhaustiveWhen:
active: false
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: false
IgnoredReturnValue:
active: false
restrictToAnnotatedMethods: true
returnValueAnnotations:
- '*.CheckResult'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- '*.CanIgnoreReturnValue'
ignoreFunctionCall: []
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: false
MissingPackageDeclaration:
active: false
excludes: ['**/*.kts']
MissingWhenCase:
active: true
allowElseExpression: true
NullCheckOnMutableProperty:
active: false
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnsafeCast:
active: true
UnusedUnaryOperator:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: true
style:
active: true
CanBeNonNullable:
active: false
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: false
maxDestructuringEntries: 3
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: false
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values:
- 'FIXME:'
- 'STOPSHIP:'
- 'TODO:'
allowedPatterns: ''
customMessage: ''
ForbiddenImport:
active: false
imports: []
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- 'kotlin.io.print'
- 'kotlin.io.println'
ForbiddenPublicDataClass:
active: true
excludes: ['**']
ignorePackages:
- '*.internal'
- '*.internal.*'
ForbiddenVoid:
active: false
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: ''
LibraryCodeMustSpecifyReturnType:
active: true
excludes: ['**']
LibraryEntitiesShouldNotBePublic:
active: true
excludes: ['**']
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesIfStatements:
active: false
MandatoryBracesLoops:
active: false
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
active: false
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
ObjectLiteralToLambda:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableLength: 4
allowNonStandardGrouping: false
UnnecessaryAbstractClass:
active: true
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryFilter:
active: false
UnnecessaryInheritance:
active: true
UnnecessaryInnerClass:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: '(_|ignored|expected|serialVersionUID)'
UseAnyOrNoneInsteadOfFind:
active: false
UseArrayLiteralsInAnnotations:
active: false
UseCheckNotNull:
active: false
UseCheckOrError:
active: false
UseDataClass:
active: false
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: false
UseOrEmpty:
active: false
UseRequire:
active: false
UseRequireNotNull:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
WildcardImport:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeImports:
- 'java.util.*'

View File

@@ -1,61 +0,0 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin)
id("convention.jvm.toolchain")
}
android {
namespace = "eu.wewox.pagecurl"
compileSdk = libs.versions.sdk.compile.get().toInt()
defaultConfig {
applicationId = "eu.wewox.pagecurl"
minSdk = libs.versions.sdk.min.get().toInt()
targetSdk = libs.versions.sdk.target.get().toInt()
versionCode = 1
versionName = "1.0"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
kotlinOptions {
freeCompilerArgs = freeCompilerArgs +
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(project(":pagecurl"))
implementation(platform(libs.compose.bom))
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.androidx.activitycompose)
implementation(libs.androidx.pagingruntime)
implementation(libs.androidx.pagingcompose)
}

View File

@@ -1,63 +0,0 @@
package eu.wewox.pagecurl
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import eu.wewox.pagecurl.screens.BackPagePageCurlScreen
import eu.wewox.pagecurl.screens.InteractionConfigInPageCurlScreen
import eu.wewox.pagecurl.screens.PagingPageCurlScreen
import eu.wewox.pagecurl.screens.SettingsPageCurlScreen
import eu.wewox.pagecurl.screens.ShadowInPageCurlScreen
import eu.wewox.pagecurl.screens.SimplePageCurlScreen
import eu.wewox.pagecurl.screens.StateInPageCurlScreen
import eu.wewox.pagecurl.ui.theme.PageCurlTheme
/**
* Main activity for demo application.
* Contains simple "Crossfade" based navigation to various examples.
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
PageCurlTheme {
var example by rememberSaveable { mutableStateOf<Example?>(null) }
BackHandler(enabled = example != null) {
example = null
}
Surface(color = MaterialTheme.colorScheme.background) {
Crossfade(targetState = example, Modifier.safeDrawingPadding(), label = "Crossfade") { selected ->
when (selected) {
null -> RootScreen(onExampleClick = { example = it })
Example.SimplePageCurl -> SimplePageCurlScreen()
Example.PagingPageCurl -> PagingPageCurlScreen()
Example.SettingsPageCurl -> SettingsPageCurlScreen()
Example.StateInPageCurl -> StateInPageCurlScreen()
Example.InteractionConfigInPageCurl -> InteractionConfigInPageCurlScreen()
Example.ShadowPageCurl -> ShadowInPageCurlScreen()
Example.BackPagePageCurl -> BackPagePageCurlScreen()
}
}
}
}
}
}
}

View File

@@ -1,48 +1,21 @@
# Project-wide Gradle settings. # Compiler Configuration
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
kotlin.code.style=official
# Maven setup with https://github.com/vanniktech/gradle-maven-publish-plugin kotlin.incremental.useClasspathSnapshot=true
SONATYPE_HOST=S01 org.jetbrains.compose.experimental.uikit.enabled=true
RELEASE_SIGNING_ENABLED=true # Project Configuration
project.name=pagecurl-multiplatform
GROUP=io.github.oleksandrbalan project.description=Page Curl library for Jetpack Compose Multiplatform.
POM_ARTIFACT_ID=pagecurl project.url=https://github.com/fankes/pagecurl-multiplatform
VERSION_NAME=1.4.1 project.groupName=eu.wewox.pagecurl
project.moduleName=pagecurl-multiplatform
POM_NAME=Page Curl project.version="1.4.1"
POM_DESCRIPTION=This library allows to create an effect of turning pages, which can be used in book reader applications, custom on-boarding screens or elsewhere. project.licence.name=Apache License 2.0
POM_INCEPTION_YEAR=2022 project.licence.url=https://github.com/fankes/pagecurl-multiplatform/blob/master/LICENSE
POM_URL=https://github.com/oleksandrbalan/pagecurl project.android.compileSdk=34
project.android.minSdk=21
POM_LICENSE_NAME=The Apache Software License, Version 2.0 project.android.targetSdk=34
POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt project.samples.androidApp.versionName=universal
POM_LICENSE_DIST=repo project.samples.androidApp.versionCode=1
POM_SCM_URL=https://github.com/oleksandrbalan/pagecurl
POM_SCM_CONNECTION=scm:git:git://github.com/oleksandrbalan/pagecurl.git
POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/oleksandrbalan/pagecurl.git
POM_DEVELOPER_ID=oleksandrbalan
POM_DEVELOPER_NAME=Oleksandr Balan
POM_DEVELOPER_URL=https://github.com/oleksandrbalan

View File

@@ -1,34 +0,0 @@
[versions]
sdk-compile = "34"
sdk-min = "21"
sdk-target = "33"
compose-bom = "2023.09.00"
compose-compiler = "1.5.3"
activity-compose = "1.8.0-beta01"
paging = "3.2.1"
plugin-android-gradle = "8.1.1"
plugin-kotlin = "1.9.10"
plugin-detekt = "1.21.0"
plugin-spotless = "6.5.1"
plugin-mavenpublish = "0.25.3"
java-toolchain = "17"
[libraries]
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
compose-foundation = { module = "androidx.compose.foundation:foundation" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-ui = { module = "androidx.compose.ui:ui" }
androidx-activitycompose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
androidx-pagingruntime = { module = "androidx.paging:paging-runtime", version.ref = "paging" }
androidx-pagingcompose = { module = "androidx.paging:paging-compose", version.ref = "paging" }
[plugins]
android-application = { id = "com.android.application", version.ref = "plugin-android-gradle" }
android-library = { id = "com.android.library", version.ref = "plugin-android-gradle" }
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "plugin-kotlin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "plugin-detekt" }
spotless = { id = "com.diffplug.spotless", version.ref = "plugin-spotless" }
mavenpublish = { id = "com.vanniktech.maven.publish", version.ref = "plugin-mavenpublish" }

View File

@@ -0,0 +1,65 @@
preferences:
autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES
repositories-mode: FAIL_ON_PROJECT_REPOS
repositories:
gradle-plugin-portal:
scope: PLUGINS
google:
maven-central:
plugins:
org.jetbrains.kotlin.multiplatform:
alias: kotlin-multiplatform
version: 1.9.10
org.jetbrains.compose:
alias: jetbrains-compose
version: 1.5.1
com.android.application:
alias: android-application
version: 8.1.2
com.android.library:
alias: android-library
version-ref: android-application
libraries:
app.cash.paging:
paging-common:
version: 3.3.0-alpha02-0.4.0
paging-compose-common:
version-ref: <this>::paging-common
androidx.compose:
compose-bom:
version: 2023.09.02
androidx.compose.foundation:
foundation:
version: <no-spec>
androidx.compose.ui:
ui:
version: <no-spec>
androidx.compose.material3:
material3:
version: <no-spec>
androidx.activity:
activity:
version: 1.8.0
activity-compose:
version: 1.7.2
androidx.core:
core-ktx:
version: 1.10.0
androidx.appcompat:
appcompat:
version: 1.6.1
com.google.android.material:
material:
version: 1.8.0
androidx.test.ext:
junit:
version: 1.1.5
androidx.test.espresso:
espresso-core:
version: 3.5.1
junit:
junit:
version: 4.13.2

View File

@@ -1,6 +1,5 @@
#Wed Sep 06 22:11:14 CEST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

178
gradlew.bat vendored
View File

@@ -1,89 +1,89 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

View File

@@ -1,32 +1,65 @@
plugins { plugins {
alias(libs.plugins.android.library) autowire(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin) autowire(libs.plugins.android.library)
alias(libs.plugins.mavenpublish) autowire(libs.plugins.jetbrains.compose)
id("convention.jvm.toolchain") }
group = property.project.groupName
kotlin {
androidTarget()
jvm("desktop")
iosX64()
iosArm64()
iosSimulatorArm64()
jvmToolchain(17)
sourceSets {
all {
languageSettings {
optIn("kotlinx.cinterop.ExperimentalForeignApi")
}
}
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
}
}
val androidMain by getting
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
}
} }
android { android {
namespace = "eu.wewox.pagecurl" namespace = property.project.groupName
compileSdk = property.project.android.compileSdk
compileSdk = libs.versions.sdk.compile.get().toInt() sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig { defaultConfig {
minSdk = libs.versions.sdk.min.get().toInt() minSdk = property.project.android.minSdk
} }
buildFeatures { compileOptions {
compose = true sourceCompatibility = JavaVersion.VERSION_17
} targetCompatibility = JavaVersion.VERSION_17
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
kotlinOptions {
freeCompilerArgs = freeCompilerArgs +
"-Xexplicit-api=strict"
} }
} }
dependencies { java {
implementation(platform(libs.compose.bom)) sourceCompatibility = JavaVersion.VERSION_17
implementation(libs.compose.foundation) targetCompatibility = JavaVersion.VERSION_17
implementation(libs.compose.ui) }
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@@ -0,0 +1,9 @@
package eu.wewox.pagecurl.page
import android.graphics.BlurMaskFilter
import androidx.compose.ui.graphics.Paint
actual fun Paint.setBlurred(value: Float) {
if (value == 0f) return
asFrameworkPaint().maskFilter = BlurMaskFilter(value, BlurMaskFilter.Blur.NORMAL)
}

View File

@@ -0,0 +1,3 @@
package eu.wewox.pagecurl.page
internal actual fun systemCurrentTimeMillis() = System.currentTimeMillis()

View File

@@ -48,7 +48,7 @@ public fun rememberPageCurlConfig(
backPageColor: Color = Color.White, backPageColor: Color = Color.White,
backPageContentAlpha: Float = 0.1f, backPageContentAlpha: Float = 0.1f,
shadowColor: Color = Color.Black, shadowColor: Color = Color.Black,
shadowAlpha: Float = 0.2f, shadowAlpha: Float = 0.5f,
shadowRadius: Dp = 15.dp, shadowRadius: Dp = 15.dp,
shadowOffset: DpOffset = DpOffset((-5).dp, 0.dp), shadowOffset: DpOffset = DpOffset((-5).dp, 0.dp),
dragForwardEnabled: Boolean = true, dragForwardEnabled: Boolean = true,

View File

@@ -1,23 +1,22 @@
package eu.wewox.pagecurl.page package eu.wewox.pagecurl.page
import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.Build
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.CacheDrawScope import androidx.compose.ui.draw.CacheDrawScope
import androidx.compose.ui.draw.DrawResult import androidx.compose.ui.draw.DrawResult
import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.toRect import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.ImageBitmapConfig
import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.rotateRad import androidx.compose.ui.graphics.drawscope.rotateRad
import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.ExperimentalPageCurlApi
@@ -25,8 +24,9 @@ import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.utils.Polygon import eu.wewox.pagecurl.utils.Polygon
import eu.wewox.pagecurl.utils.lineLineIntersection import eu.wewox.pagecurl.utils.lineLineIntersection
import eu.wewox.pagecurl.utils.rotate import eu.wewox.pagecurl.utils.rotate
import java.lang.Float.max import kotlin.math.PI
import kotlin.math.atan2 import kotlin.math.atan2
import kotlin.math.max
@ExperimentalPageCurlApi @ExperimentalPageCurlApi
internal fun Modifier.drawCurl( internal fun Modifier.drawCurl(
@@ -157,7 +157,7 @@ private fun CacheDrawScope.prepareCurl(
// Calculate the angle in radians between X axis and the curl line, this is used to rotate mirrored content to the // Calculate the angle in radians between X axis and the curl line, this is used to rotate mirrored content to the
// right position of the curled back-page // right position of the curled back-page
val lineVector = topCurlOffset - bottomCurlOffset val lineVector = topCurlOffset - bottomCurlOffset
val angle = Math.PI.toFloat() - atan2(lineVector.y, lineVector.x) * 2 val angle = PI.toFloat() - atan2(lineVector.y, lineVector.x) * 2
// Prepare a lambda to draw the shadow of the back-page // Prepare a lambda to draw the shadow of the back-page
val drawShadow = prepareShadow(config, polygon, angle) val drawShadow = prepareShadow(config, polygon, angle)
@@ -197,29 +197,27 @@ private fun CacheDrawScope.prepareShadow(
// Prepare shadow parameters // Prepare shadow parameters
val radius = config.shadowRadius.toPx() val radius = config.shadowRadius.toPx()
val shadowColor = config.shadowColor.copy(alpha = config.shadowAlpha).toArgb() val shadowColor = config.shadowColor.copy(alpha = config.shadowAlpha).toArgb()
val transparent = config.shadowColor.copy(alpha = 0f).toArgb() // TODO shadowOffset to be set here
val shadowOffset = Offset(-config.shadowOffset.x.toPx(), config.shadowOffset.y.toPx()) val shadowOffset = Offset(-config.shadowOffset.x.toPx(), config.shadowOffset.y.toPx())
.rotate(2 * Math.PI.toFloat() - angle) .rotate(2 * PI.toFloat() - angle)
// Prepare shadow paint with a shadow layer // Prepare shadow paint with a shadow layer
val paint = Paint().apply { val paint = Paint().apply {
val frameworkPaint = asFrameworkPaint() color = Color(shadowColor)
frameworkPaint.color = transparent setBlurred(radius)
frameworkPaint.setShadowLayer(
config.shadowRadius.toPx(), // val frameworkPaint = asFrameworkPaint()
shadowOffset.x, // frameworkPaint.color = transparent
shadowOffset.y, // frameworkPaint.setShadowLayer(
shadowColor // config.shadowRadius.toPx(),
) // shadowOffset.x,
// shadowOffset.y,
// shadowColor
// )
} }
// Hardware acceleration supports setShadowLayer() only on API 28 and above, thus to support previous API versions
// draw a shadow to the bitmap instead // draw a shadow to the bitmap instead
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { return prepareShadowImage(radius, paint, polygon)
prepareShadowApi28(radius, paint, polygon)
} else {
prepareShadowImage(radius, paint, polygon)
}
} }
private fun prepareShadowApi28( private fun prepareShadowApi28(
@@ -228,12 +226,7 @@ private fun prepareShadowApi28(
polygon: Polygon, polygon: Polygon,
): ContentDrawScope.() -> Unit = { ): ContentDrawScope.() -> Unit = {
drawIntoCanvas { drawIntoCanvas {
it.nativeCanvas.drawPath( it.drawPath(polygon.offset(radius).toPath(), paint)
polygon
.offset(radius).toPath()
.asAndroidPath(),
paint.asFrameworkPaint()
)
} }
} }
@@ -243,26 +236,26 @@ private fun CacheDrawScope.prepareShadowImage(
polygon: Polygon, polygon: Polygon,
): ContentDrawScope.() -> Unit { ): ContentDrawScope.() -> Unit {
// Increase the size a little bit so that shadow is not clipped // Increase the size a little bit so that shadow is not clipped
val bitmap = Bitmap.createBitmap( val bitmap = ImageBitmap(
(size.width + radius * 4).toInt(), (size.width + radius * 4).toInt(),
(size.height + radius * 4).toInt(), (size.height + radius * 4).toInt(),
Bitmap.Config.ARGB_8888 ImageBitmapConfig.Argb8888
) )
Canvas(bitmap).apply { Canvas(bitmap).apply {
drawPath( drawPath(
polygon polygon
// As bitmap size is increased we should translate the polygon so that shadow remains in center // As bitmap size is increased we should translate the polygon so that shadow remains in center
.translate(Offset(2 * radius, 2 * radius)) .translate(Offset(2 * radius, 2 * radius))
.offset(radius).toPath() .offset(radius).toPath(), paint
.asAndroidPath(),
paint.asFrameworkPaint()
) )
} }
return { return {
drawIntoCanvas { drawIntoCanvas {
// As bitmap size is increased we should shift the drawing so that shadow remains in center // As bitmap size is increased we should shift the drawing so that shadow remains in center
it.nativeCanvas.drawBitmap(bitmap, -2 * radius, -2 * radius, null) it.drawImage(bitmap, Offset(-2 * radius, -2 * radius), paint)
} }
} }
} }
internal expect fun Paint.setBlurred(value: Float)

View File

@@ -5,9 +5,9 @@ import androidx.compose.animation.core.AnimationVector4D
import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.calculateTargetValue import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.splineBasedDecay import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.drag import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
@@ -84,50 +84,50 @@ internal fun Modifier.curlGesture(
val velocityTracker = VelocityTracker() val velocityTracker = VelocityTracker()
val startRect by lazy { targetStart.multiply(size) } val startRect by lazy { targetStart.multiply(size) }
val endRect by lazy { targetEnd.multiply(size) } val endRect by lazy { targetEnd.multiply(size) }
forEachGesture { awaitEachGesture {
awaitPointerEventScope { val down = awaitFirstDown(requireUnconsumed = false)
val down = awaitFirstDown(requireUnconsumed = false) if (!startRect.contains(down.position)) {
if (!startRect.contains(down.position)) { return@awaitEachGesture
return@awaitPointerEventScope }
}
// Change X position to be always on the right side for more convenient gesture tracking // Change X position to be always on the right side for more convenient gesture tracking
val dragStart = down.position.copy(x = size.width.toFloat()) val dragStart = down.position.copy(x = size.width.toFloat())
onStart() onStart()
var dragCurrent = dragStart var dragCurrent = dragStart
drag(down.id) { change -> drag(down.id) { change ->
dragCurrent = change.position dragCurrent = change.position
velocityTracker.addPosition(System.currentTimeMillis(), dragCurrent) velocityTracker.addPosition(systemCurrentTimeMillis(), dragCurrent)
change.consume() change.consume()
val vector = (dragStart - dragCurrent).rotate(PI.toFloat() / 2) val vector = (dragStart - dragCurrent).rotate(PI.toFloat() / 2)
onCurl(dragCurrent - vector, dragCurrent + vector) onCurl(dragCurrent - vector, dragCurrent + vector)
} }
if (dragCurrent == dragStart) { if (dragCurrent == dragStart) {
onCancel() onCancel()
return@awaitPointerEventScope return@awaitEachGesture
} }
val velocity = velocityTracker.calculateVelocity() val velocity = velocityTracker.calculateVelocity()
val decay = splineBasedDecay<Offset>(this) val decay = splineBasedDecay<Offset>(this)
val target = decay.calculateTargetValue( val target = decay.calculateTargetValue(
Offset.VectorConverter, Offset.VectorConverter,
dragCurrent, dragCurrent,
Offset(velocity.x, velocity.y) Offset(velocity.x, velocity.y)
).let { ).let {
Offset( Offset(
it.x.coerceIn(0f, size.width.toFloat() - 1), it.x.coerceIn(0f, size.width.toFloat() - 1),
it.y.coerceIn(0f, size.height.toFloat() - 1) it.y.coerceIn(0f, size.height.toFloat() - 1)
) )
} }
if (endRect.contains(target)) { if (endRect.contains(target)) {
onEnd() onEnd()
} else { } else {
onCancel() onCancel()
}
} }
} }
} }
internal expect fun systemCurrentTimeMillis(): Long

View File

@@ -0,0 +1,51 @@
package eu.wewox.pagecurl.page
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import eu.wewox.pagecurl.ExperimentalPageCurlApi
import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.utils.multiply
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ExperimentalPageCurlApi
internal fun Modifier.tapGesture(
config: PageCurlConfig,
scope: CoroutineScope,
onTapForward: suspend () -> Unit,
onTapBackward: suspend () -> Unit,
): Modifier = pointerInput(config) {
awaitEachGesture {
val down = awaitFirstDown().also { it.consume() }
val up = waitForUpOrCancellation() ?: return@awaitEachGesture
if ((down.position - up.position).getDistance() > viewConfiguration.touchSlop) {
return@awaitEachGesture
}
if (config.tapCustomEnabled && config.onCustomTap(this, size, up.position)) {
return@awaitEachGesture
}
if (config.tapForwardEnabled && config.tapForwardInteraction.target.multiply(size).contains(up.position)) {
scope.launch {
onTapForward()
}
return@awaitEachGesture
}
if (config.tapBackwardEnabled &&
config.tapBackwardInteraction.target
.multiply(size)
.contains(up.position)
) {
scope.launch {
onTapBackward()
}
return@awaitEachGesture
}
}
}

View File

@@ -0,0 +1,10 @@
package eu.wewox.pagecurl.page
import androidx.compose.ui.graphics.Paint
import org.jetbrains.skia.FilterBlurMode
import org.jetbrains.skia.MaskFilter
actual fun Paint.setBlurred(value: Float) {
if (value == 0f) return
asFrameworkPaint().maskFilter = MaskFilter.makeBlur(FilterBlurMode.NORMAL, value / 2f)
}

View File

@@ -0,0 +1,3 @@
package eu.wewox.pagecurl.page
internal actual fun systemCurrentTimeMillis() = System.currentTimeMillis()

View File

@@ -0,0 +1,10 @@
package eu.wewox.pagecurl.page
import androidx.compose.ui.graphics.Paint
import org.jetbrains.skia.FilterBlurMode
import org.jetbrains.skia.MaskFilter
actual fun Paint.setBlurred(value: Float) {
if (value == 0f) return
asFrameworkPaint().maskFilter = MaskFilter.makeBlur(FilterBlurMode.NORMAL, value / 2f)
}

View File

@@ -0,0 +1,13 @@
package eu.wewox.pagecurl.page
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import platform.posix.gettimeofday
import platform.posix.timeval
internal actual fun systemCurrentTimeMillis() = memScoped {
val timeVal = alloc<timeval>()
gettimeofday(timeVal.ptr, null)
(timeVal.tv_sec * 1000) + (timeVal.tv_usec / 1000)
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.wewox.pagecurl" />

View File

@@ -1,53 +0,0 @@
package eu.wewox.pagecurl.page
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import eu.wewox.pagecurl.ExperimentalPageCurlApi
import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.utils.multiply
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ExperimentalPageCurlApi
internal fun Modifier.tapGesture(
config: PageCurlConfig,
scope: CoroutineScope,
onTapForward: suspend () -> Unit,
onTapBackward: suspend () -> Unit,
): Modifier = pointerInput(config) {
forEachGesture {
awaitPointerEventScope {
val down = awaitFirstDown().also { it.consume() }
val up = waitForUpOrCancellation() ?: return@awaitPointerEventScope
if ((down.position - up.position).getDistance() > viewConfiguration.touchSlop) {
return@awaitPointerEventScope
}
if (config.tapCustomEnabled && config.onCustomTap(this, size, up.position)) {
return@awaitPointerEventScope
}
if (config.tapForwardEnabled && config.tapForwardInteraction.target.multiply(size).contains(up.position)) {
scope.launch {
onTapForward()
}
return@awaitPointerEventScope
}
if (config.tapBackwardEnabled &&
config.tapBackwardInteraction.target
.multiply(size)
.contains(up.position)
) {
scope.launch {
onTapBackward()
}
return@awaitPointerEventScope
}
}
}
}

View File

@@ -0,0 +1,45 @@
plugins {
autowire(libs.plugins.kotlin.multiplatform)
autowire(libs.plugins.android.application)
autowire(libs.plugins.jetbrains.compose)
}
group = property.project.groupName
kotlin {
androidTarget()
jvmToolchain(17)
sourceSets {
val androidMain by getting {
dependencies {
implementation(projects.samples.shared)
}
}
}
}
android {
namespace = property.project.groupName
compileSdk = property.project.android.compileSdk
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
applicationId = property.project.groupName
minSdk = property.project.android.minSdk
targetSdk = property.project.android.targetSdk
versionName = property.project.samples.androidApp.versionName
versionCode = property.project.samples.androidApp.versionCode
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

View File

@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here. # Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the # You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle. # proguardFiles setting in build.gradle.kts.
# #
# For more details, see # For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html # http://developer.android.com/guide/developing/tools/proguard.html

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="eu.wewox.pagecurl">
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,26 @@
package eu.wewox.pagecurl
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.addCallback
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { App(Modifier.safeDrawingPadding()) }
val callback = onBackPressedDispatcher.addCallback(enabled = false) {
isEnabled = false
PageStateHandler.navToMain()
}
PageStateHandler.onLeftMain { callback.isEnabled = true }
}
}

View File

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 286 KiB

View File

Before

Width:  |  Height:  |  Size: 607 KiB

After

Width:  |  Height:  |  Size: 607 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,29 @@
plugins {
autowire(libs.plugins.kotlin.multiplatform)
autowire(libs.plugins.jetbrains.compose)
}
group = property.project.groupName
kotlin {
jvm("desktop")
jvmToolchain(17)
sourceSets {
val desktopMain by getting {
dependencies {
implementation(projects.samples.shared)
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
compose.desktop {
application {
mainClass = "${property.project.groupName}.MainKt"
}
}

View File

@@ -0,0 +1,14 @@
package eu.wewox.pagecurl
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "PageCurl Demo",
state = rememberWindowState(width = 450.dp, height = 750.dp)
) { App() }
}

6
samples/iosApp/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
## User settings
xcuserdata/
## Xcode 8 and earlier
*.xcscmblueprint
*.xccheckout

View File

@@ -0,0 +1,3 @@
TEAM_ID=
BUNDLE_ID=com.myapplication.MyApplication
APP_NAME=PageCurl

View File

@@ -0,0 +1,400 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* PageCurl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PageCurl.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
F85CB1118929364A9C6EFABC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
058557D7273AAEEB004C7B11 /* Preview Content */ = {
isa = PBXGroup;
children = (
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
42799AB246E5F90AF97AA0EF /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
AB1DB47929225F7C00F7AF9C /* Configuration */,
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
42799AB246E5F90AF97AA0EF /* Frameworks */,
);
sourceTree = "<group>";
};
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* PageCurl.app */,
);
name = Products;
sourceTree = "<group>";
};
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iOSApp.swift */,
058557D7273AAEEB004C7B11 /* Preview Content */,
);
path = iosApp;
sourceTree = "<group>";
};
AB1DB47929225F7C00F7AF9C /* Configuration */ = {
isa = PBXGroup;
children = (
AB3632DC29227652001CCB65 /* Config.xcconfig */,
);
path = Configuration;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* iosApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
05D91A912A5EF49C00F138EB /* Compile Kotlin */,
7555FF77242A565900829871 /* Sources */,
7555FF79242A565900829871 /* Resources */,
F85CB1118929364A9C6EFABC /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = iosApp;
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* PageCurl.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = orgName;
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
};
};
};
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7555FF72242A565900829871;
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7555FF7A242A565900829871 /* iosApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
05D91A912A5EF49C00F138EB /* Compile Kotlin */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Compile Kotlin";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd \"$(dirname \"$(dirname \"$SRCROOT\")\")\"\n./gradlew :samples:shared:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)\n",
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n",
);
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
"shared\n$(inherited)",
"-framework",
"shared\n",
);
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
PRODUCT_NAME = "${APP_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
DEVELOPMENT_TEAM = "${TEAM_ID}";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)\n",
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n",
);
INFOPLIST_FILE = iosApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
"shared\n$(inherited)",
"-framework",
"shared\n",
);
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
PRODUCT_NAME = "${APP_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "app-icon-512.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,19 @@
import UIKit
import SwiftUI
import shared
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let viewController = Main_iosKt.createUIViewController()
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
ComposeView()
.ignoresSafeArea(.all, edges: .bottom) // Compose has own keyboard handler
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,10 @@
import SwiftUI
@main
struct iOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

1
samples/shared/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,87 @@
plugins {
autowire(libs.plugins.kotlin.multiplatform)
autowire(libs.plugins.android.library)
autowire(libs.plugins.jetbrains.compose)
}
group = property.project.groupName
kotlin {
androidTarget()
jvm("desktop")
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}
jvmToolchain(17)
sourceSets {
all {
languageSettings {
optIn("androidx.compose.material3.ExperimentalMaterial3Api")
optIn("eu.wewox.pagecurl.ExperimentalPageCurlApi")
}
}
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(app.cash.paging.paging.common)
implementation(app.cash.paging.paging.compose.common)
implementation(projects.pagecurl)
}
}
val androidMain by getting {
dependencies {
api(compose.foundation)
api(androidx.core.core.ktx)
api(androidx.appcompat.appcompat)
api(androidx.activity.activity)
api(androidx.activity.activity.compose)
}
}
val desktopMain by getting {
dependencies {
api(compose.desktop.currentOs)
api(compose.runtime)
api(compose.foundation)
api(compose.material3)
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
}
}
android {
namespace = property.project.groupName
compileSdk = property.project.android.compileSdk
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = property.project.android.minSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -0,0 +1,8 @@
package eu.wewox.pagecurl
import androidx.compose.runtime.Composable
@Composable
internal actual fun BackablePageScreen(content: @Composable () -> Unit) {
content()
}

View File

@@ -0,0 +1,62 @@
package eu.wewox.pagecurl
import androidx.compose.animation.Crossfade
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import eu.wewox.pagecurl.screens.BackPagePageCurlScreen
import eu.wewox.pagecurl.screens.InteractionConfigInPageCurlScreen
import eu.wewox.pagecurl.screens.PagingPageCurlScreen
import eu.wewox.pagecurl.screens.SettingsPageCurlScreen
import eu.wewox.pagecurl.screens.ShadowInPageCurlScreen
import eu.wewox.pagecurl.screens.SimplePageCurlScreen
import eu.wewox.pagecurl.screens.StateInPageCurlScreen
import eu.wewox.pagecurl.ui.theme.PageCurlTheme
/**
* Main view for demo application.
* Contains simple "Crossfade" based navigation to various examples.
*/
@Composable
fun App(modifier: Modifier = Modifier) {
PageCurlTheme {
var example by rememberSaveable { mutableStateOf<Example?>(null) }
PageStateHandler.callNavToMain = { example = null }
Surface(color = MaterialTheme.colorScheme.background) {
Crossfade(targetState = example, modifier, label = "Crossfade") { selected ->
when (selected) {
null -> RootScreen(onExampleClick = { PageStateHandler.onLeftMain?.invoke(); example = it })
Example.SimplePageCurl -> BackablePageScreen { SimplePageCurlScreen() }
Example.PagingPageCurl -> BackablePageScreen { PagingPageCurlScreen() }
Example.SettingsPageCurl -> BackablePageScreen { SettingsPageCurlScreen() }
Example.StateInPageCurl -> BackablePageScreen { StateInPageCurlScreen() }
Example.InteractionConfigInPageCurl -> BackablePageScreen { InteractionConfigInPageCurlScreen() }
Example.ShadowPageCurl -> BackablePageScreen { ShadowInPageCurlScreen() }
Example.BackPagePageCurl -> BackablePageScreen { BackPagePageCurlScreen() }
}
}
}
}
}
object PageStateHandler {
internal var onLeftMain: (() -> Unit)? = null
internal var callNavToMain: (() -> Unit)? = null
fun onLeftMain(onLeftMain: () -> Unit) {
this.onLeftMain = onLeftMain
}
fun navToMain() {
callNavToMain?.invoke()
}
}
@Composable
internal expect fun BackablePageScreen(content: @Composable () -> Unit)

View File

@@ -1,5 +1,3 @@
@file:OptIn(ExperimentalPageCurlApi::class)
package eu.wewox.pagecurl.components package eu.wewox.pagecurl.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -20,7 +18,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
import eu.wewox.pagecurl.ExperimentalPageCurlApi
import eu.wewox.pagecurl.config.PageCurlConfig import eu.wewox.pagecurl.config.PageCurlConfig
@Composable @Composable

Some files were not shown because too many files have changed in this diff Show More