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

1
samples/.gitignore vendored Normal file
View File

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

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

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

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
}
}

21
samples/androidApp/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PageCurl">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.PageCurl">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

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

@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="2.475"
android:scaleY="2.475"
android:translateX="34.2"
android:translateY="34.2">
<path
android:fillColor="#FFF281"
android:pathData="M4,0.375H12C14.002,0.375 15.625,1.9981 15.625,4.0001V4.0052V4.0103V4.0154V4.0205V4.0256V4.0307V4.0358V4.0409V4.046V4.0511V4.0561V4.0612V4.0663V4.0714V4.0765V4.0816V4.0867V4.0917V4.0968V4.1019V4.107V4.1121V4.1171V4.1222V4.1273V4.1324V4.1374V4.1425V4.1476V4.1526V4.1577V4.1628V4.1678V4.1729V4.178V4.183V4.1881V4.1932V4.1982V4.2033V4.2083V4.2134V4.2184V4.2235V4.2285V4.2336V4.2386V4.2437V4.2487V4.2538V4.2588V4.2639V4.2689V4.274V4.279V4.284V4.2891V4.2941V4.2991V4.3042V4.3092V4.3142V4.3193V4.3243V4.3293V4.3343V4.3394V4.3444V4.3494V4.3544V4.3594V4.3644V4.3695V4.3745V4.3795V4.3845V4.3895V4.3945V4.3995V4.4045V4.4095V4.4145V4.4195V4.4245V4.4295V4.4345V4.4395V4.4445V4.4495V4.4545V4.4595V4.4644V4.4694V4.4744V4.4794V4.4844V4.4893V4.4943V4.4993V4.5043V4.5092V4.5142V4.5192V4.5241V4.5291V4.5341V4.539V4.544V4.5489V4.5539V4.5588V4.5638V4.5687V4.5737V4.5786V4.5836V4.5885V4.5934V4.5984V4.6033V4.6083V4.6132V4.6181V4.623V4.628V4.6329V4.6378V4.6427V4.6476V4.6526V4.6575V4.6624V4.6673V4.6722V4.6771V4.682V4.6869V4.6918V4.6967V4.7016V4.7065V4.7114V4.7163V4.7211V4.726V4.7309V4.7358V4.7407V4.7456V4.7504V4.7553V4.7602V4.765V4.7699V4.7748V4.7796V4.7845V4.7893V4.7942V4.799V4.8039V4.8087V4.8136V4.8184V4.8232V4.8281V4.8329V4.8377V4.8426V4.8474V4.8522V4.8571V4.8619V4.8667V4.8715V4.8763V4.8811V4.8859V4.8907V4.8955V4.9004V4.9051V4.9099V4.9147V4.9195V4.9243V4.9291V4.9339V4.9387V4.9434V4.9482V4.953V4.9578V4.9625V4.9673V4.9721V4.9768V4.9816V4.9863V4.9911V4.9958V5.0006V5.0053V5.01V5.0148V5.0195V5.0243V5.029V5.0337V5.0384V5.0432V5.0479V5.0526V5.0573V5.062V5.0667V5.0714V5.0761V5.0808V5.0855V5.0902V5.0949V5.0996V5.1043V5.109V5.1136V5.1183V5.123V5.1277V5.1323V5.137V5.1417V5.1463V5.151V5.1556V5.1603V5.1649V5.1696V5.1742V5.1788V5.1835V5.1881V5.1927V5.1974V5.202V5.2066V5.2112V5.2158V5.2204V5.225V5.2296V5.2342V5.2388V5.2434V5.248V5.2526V5.2572V5.2618V5.2663V5.2709V5.2755V5.2801V5.2846V5.2892V5.2937V5.2983V5.3029V5.3074V5.312V5.3165V5.321V5.3256V5.3301V5.3346V5.3391V5.3437V5.3482V5.3527V5.3572V5.3617V5.3662V5.3707V5.3752V5.3797V5.3842V5.3887V5.3932V5.3977V5.4021V5.4066V5.4111V5.4155V5.42V5.4245V5.4289V5.4334V5.4378V5.4423V5.4467V5.4511V5.4556V5.46V5.4644V5.4689V5.4733V5.4777V5.4821V5.4865V5.4909V5.4953V5.4997V5.5041V5.5085V5.5129V5.5173V5.5216V5.526V5.5304V5.5348V5.5391V5.5435V5.5478V5.5522V5.5565V5.5609V5.5652V5.5696V5.5739V5.5782V5.5826V5.5869V5.5912V5.5955V5.5998V5.6041V5.6084V5.6127V5.617V5.6213V5.6256V5.6299V5.6341V5.6384V5.6427V5.6469V5.6512V5.6555V5.6597V5.664V5.6682V5.6725V5.6767V5.6809V5.6852V5.6894V5.6936V5.6978V5.702V5.7062V5.7104V5.7146V5.7188V5.723V5.7272V5.7314V5.7356V5.7397V5.7439V5.7481V5.7523V5.7564V5.7606V5.7647V5.7689V5.773V5.7771V5.7813V5.7854V5.7895V5.7936V5.7977V5.8019V5.806V5.8101V5.8142V5.8183V5.8223V5.8264V5.8305V5.8346V5.8387V5.8427V5.8468V5.8508V5.8549V5.8589V5.863V5.867V5.871V5.8751V5.8791V5.8831V5.8871V5.8911V5.8952V5.8992V5.9032V5.9071V5.9111V5.9151V5.9191V5.9231V5.927V5.931V5.935V5.9389V5.9429V5.9468V5.9508V5.9547V5.9586V5.9626V5.9665V5.9704V5.9743V5.9782V5.9821V5.986V5.9899V5.9938V5.9977V6.0016V6.0054V6.0093V6.0132V6.017V6.0209V6.0247V6.0286V6.0324V6.0363V6.0401V6.0439V6.0477V6.0515V6.0554V6.0592V6.063V6.0668V6.0705V6.0743V6.0781V6.0819V6.0857V6.0894V6.0932V6.0969V6.1007V6.1044V6.1082V6.1119V6.1156V6.1194V6.1231V6.1268V6.1305V6.1342V6.1379V6.1416V6.1453V6.149V6.1526V6.1563V6.16V6.1636V6.1673V6.1709V6.1746V6.1782V6.1819V6.1855V6.1891V6.1927V6.1964V6.2V6.2036V6.2072V6.2108V6.2144V6.2179V6.2215V6.2251V6.2287V6.2322V6.2358V6.2393V6.2428V6.2464V6.2499V6.2534V6.257V6.2605V6.264V6.2675V6.271V6.2745V6.278V6.2815V6.2849V6.2884V6.2919V6.2954C15.625,6.6481 15.4851,6.9846 15.2348,7.2348L7.2348,15.2348C6.985,15.4847 6.6462,15.625 6.2929,15.625H4C1.998,15.625 0.375,14.002 0.375,12V4C0.375,1.998 1.998,0.375 4,0.375Z"
android:strokeWidth="0.75"
android:strokeColor="#000000" />
<path
android:fillColor="#FFF9C2"
android:pathData="M10,6.375H15.5902C15.5151,6.7764 15.3206,7.1491 15.0277,7.4419L11.7348,10.7348L7.4419,15.0277C7.1491,15.3206 6.7764,15.5151 6.375,15.5902V10C6.375,7.998 7.998,6.375 10,6.375Z"
android:strokeWidth="0.75"
android:strokeColor="#000000" />
<path
android:fillColor="#6DD3FF"
android:pathData="M7.4057,15.1246L15.1251,7.4053V11.4979C15.1251,13.4994 13.503,15.1221 11.5016,15.1229L7.4057,15.1246Z"
android:strokeWidth="0.75"
android:strokeColor="#000000" />
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">PageCurl</string>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.PageCurl" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

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

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

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

@@ -0,0 +1,41 @@
package eu.wewox.pagecurl
/**
* Enumeration of available demo examples.
*
* @param label Example name.
* @param description Brief description.
*/
enum class Example(
val label: String,
val description: String,
) {
SimplePageCurl(
"Simple Page Curl",
"Basic PageCurl usage"
),
PagingPageCurl(
"PageCurl with lazy paging",
"Example how component could be used with paging implementation"
),
SettingsPageCurl(
"Page Curl With Settings",
"Showcases how individual interactions can be toggled on / off"
),
StateInPageCurl(
"Page Curl With State Management",
"Example how state can be used to change current page (snap / animate)"
),
InteractionConfigInPageCurl(
"Interactions Configurations In Page Curl",
"Example interactions (drag / tap) can be customized"
),
ShadowPageCurl(
"Shadow Configuration in Page Curl",
"Example how to customize shadow of the page"
),
BackPagePageCurl(
"Back-Page Configuration in Page Curl",
"Example how to customize the back-page (the back of the page user see during the drag or animation)"
),
}

View File

@@ -0,0 +1,139 @@
@file:Suppress("MaxLineLength", "UndocumentedPublicProperty")
package eu.wewox.pagecurl
/**
* The model for data for the page.
*
* @property title The title to show on the page.
* @property message The message to show on the page.
*/
data class HowToPageData(
val title: String,
val message: String,
) {
companion object {
val simpleHowToPages = listOf(
HowToPageData(
"Welcome \uD83D\uDC4B",
"This is a simple demo of the PageCurl. Swipe to the left to turn the page.",
),
HowToPageData(
"Forward & backward",
"Nice, now try another direction to go backward.",
),
HowToPageData(
"Taps",
"You may also just tap in the right half of the screen to go forward and tap on the left one to go backward.",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
val interactionHowToPages = listOf(
HowToPageData(
"Interaction example",
"This example demonstrates how drag & tap gestures can be toggled on or off. By default all gestures are allowed.",
),
HowToPageData(
"Custom tap",
"This example has a custom tap configured to show a settings popup. Try it and tap somewhere near the center of the page.",
),
HowToPageData(
"Settings",
"Try to disable forward / backward drags and / or forward / backward taps.",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
val stateHowToPages = listOf(
HowToPageData(
"State example",
"This example demonstrates how state object can be used to change current page.",
),
HowToPageData(
"Custom tap",
"This example has a custom tap configured to show a settings row below. Try it and tap somewhere near the center of the page. Tap on the PageCurl to zoom back in.",
),
HowToPageData(
"Settings",
"Use buttons to go to the first / last page, or try to snap / animate forward or backward. The .snapTo() method changes the current page immediately, but .next() and .prev() methods changes the current page with a default animation. This animation could be customized, see DefaultNext and DefaultPrev in library sources.",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
val interactionSettingsHowToPages = listOf(
HowToPageData(
"Another interaction example",
"This example demonstrates how drag & tap gestures zones can be configured. By default it is right half to go forward and left half to go backward.",
),
HowToPageData(
"Custom tap",
"This example has a custom tap configured to show a settings row below. Try it and tap somewhere near the center of the page. Tap on the PageCurl to zoom back in.",
),
HowToPageData(
"Settings",
"Try to change the slider value and see how it changes the gesture zones. For example if you set 0.25f on the tap gesture this means, that the first 25% of the width will be dedicated for backward tap, and other 75% will be used for forward tap.",
),
HowToPageData(
"End region in drag",
"Keep in mind, that drag gestures have 'end' region (where gesture should be ended to complete a drag), so if you set 0f for drag gesture, the only forward gesture will be allowed, but it could not be completed. You can modify the 'end' region as needed for your use-case.",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
val shadowHowToPages = listOf(
HowToPageData(
"Shadow configuration",
"This example demonstrates how shadow can be configured.",
),
HowToPageData(
"Custom tap",
"This example has a custom tap configured to show a settings row below. Try it and tap somewhere near the center of the page. Tap on the PageCurl to zoom back in.",
),
HowToPageData(
"Settings",
"Try to change different sliders. Settings should be self-descriptive. You may also change the shadow color (not present in the example).",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
val backPageHowToPages = listOf(
HowToPageData(
"Back-page configuration",
"This example demonstrates how back-page can be configured.",
),
HowToPageData(
"Custom tap",
"This example has a custom tap configured to show a settings row below. Try it and tap somewhere near the center of the page. Tap on the PageCurl to zoom back in.",
),
HowToPageData(
"Settings",
"The alpha slider configures how much content is visible on the back page. Bigger value means that page is more see-through.",
),
HowToPageData(
"Settings",
"The color value defines a color of the back-page. It is more visible is the alpha value is low.",
),
HowToPageData(
"End",
"That is the last page, you cannot go further \uD83D\uDE09",
)
)
}
}

View File

@@ -0,0 +1,54 @@
package eu.wewox.pagecurl
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import eu.wewox.pagecurl.components.TopBar
import eu.wewox.pagecurl.ui.SpacingMedium
@Composable
internal fun RootScreen(onExampleClick: (Example) -> Unit) {
Scaffold(
topBar = { TopBar("PageCurl Demo") }
) { padding ->
LazyColumn(Modifier.padding(padding)) {
items(Example.entries) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { onExampleClick(it) }
.padding(SpacingMedium)
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = it.label,
style = MaterialTheme.typography.titleMedium
)
Text(
text = it.description,
style = MaterialTheme.typography.bodyMedium
)
}
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = null
)
}
}
}
}
}

View File

@@ -0,0 +1,68 @@
package eu.wewox.pagecurl.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.ui.SpacingLarge
import eu.wewox.pagecurl.ui.SpacingMedium
/**
* The simple page to use for demo purposes.
*
* @param index The index of the page to show a page number in the bottom.
* @param page The page data to show.
* @param modifier The modifier for this composable.
*/
@Composable
fun HowToPage(
index: Int,
page: HowToPageData,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
Column(
verticalArrangement = Arrangement.spacedBy(SpacingMedium, Alignment.CenterVertically),
modifier = Modifier
.fillMaxSize()
.padding(SpacingLarge)
) {
Text(
text = page.title,
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = page.message,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
Text(
text = index.toString(),
color = MaterialTheme.colorScheme.background,
modifier = Modifier
.align(Alignment.BottomEnd)
.background(MaterialTheme.colorScheme.onBackground, RoundedCornerShape(topStartPercent = 100))
.padding(SpacingMedium)
)
}
}

View File

@@ -0,0 +1,93 @@
package eu.wewox.pagecurl.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import eu.wewox.pagecurl.config.PageCurlConfig
@Composable
internal fun SettingsPopup(
config: PageCurlConfig,
onDismiss: () -> Unit,
) {
Popup(
alignment = Alignment.Center,
properties = PopupProperties(focusable = true),
onDismissRequest = onDismiss,
) {
Card(
shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(8.dp),
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.width(IntrinsicSize.Max)
.padding(8.dp)
) {
val switchRowModifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp)
SwitchRow(
text = "Forward drag enabled",
enabled = config.dragForwardEnabled,
onChanged = { config.dragForwardEnabled = it },
modifier = switchRowModifier
)
SwitchRow(
text = "Backward drag enabled",
enabled = config.dragBackwardEnabled,
onChanged = { config.dragBackwardEnabled = it },
modifier = switchRowModifier
)
SwitchRow(
text = "Forward tap enabled",
enabled = config.tapForwardEnabled,
onChanged = { config.tapForwardEnabled = it },
modifier = switchRowModifier
)
SwitchRow(
text = "Backward tap enabled",
enabled = config.tapBackwardEnabled,
onChanged = { config.tapBackwardEnabled = it },
modifier = switchRowModifier
)
}
}
}
}
@Composable
private fun SwitchRow(
text: String,
enabled: Boolean,
onChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
Text(text = text)
Switch(
checked = enabled,
onCheckedChange = onChanged
)
}
}

View File

@@ -0,0 +1,47 @@
package eu.wewox.pagecurl.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
/**
* The reusable component for top bar.
*
* @param title The text to show in top bar.
* @param modifier The modifier instance for the root composable.
*/
@Composable
fun TopBar(
title: String,
modifier: Modifier = Modifier,
onBackClick: (() -> Unit)? = null,
) {
TopAppBar(
title = {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
)
},
navigationIcon = {
if (onBackClick != null) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back button"
)
}
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(Color.Transparent),
modifier = modifier,
)
}

View File

@@ -0,0 +1,107 @@
package eu.wewox.pagecurl.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import eu.wewox.pagecurl.config.PageCurlConfig
/**
* Layout which could be zoomed out and zoomed in to show / hide the [bottom] bar.
*
* @param zoomOut True when layout is zoomed out.
* @param config The [PageCurlConfig] to turn off interactions in the page curl.
* @param bottom The content of the bottom bar.
* @param modifier The modifier for this composable.
* @param pageCurl The content where PageCurl should be placed.
*/
@Composable
fun ZoomOutLayout(
zoomOut: Boolean,
config: PageCurlConfig,
bottom: @Composable () -> Unit,
modifier: Modifier = Modifier,
pageCurl: @Composable () -> Unit,
) {
// Disable all state interactions when PageCurl is zoomed out
LaunchedEffect(zoomOut, config) {
with(config) {
dragForwardEnabled = !zoomOut
dragBackwardEnabled = !zoomOut
tapForwardEnabled = !zoomOut
tapBackwardEnabled = !zoomOut
}
}
ZoomOutLayout(
zoomOut = zoomOut,
bottom = bottom,
modifier = modifier,
) {
// Animate radius and elevation with the same value, because why not :)
val cornersAndElevation by animateDpAsState(if (zoomOut) 16.dp else 0.dp)
if (cornersAndElevation != 0.dp) {
Card(
shape = RoundedCornerShape(cornersAndElevation),
elevation = CardDefaults.cardElevation(cornersAndElevation),
content = { pageCurl() },
)
} else {
pageCurl()
}
}
}
@Composable
private fun ZoomOutLayout(
zoomOut: Boolean,
bottom: @Composable () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Layout(
modifier = modifier,
content = {
content()
Box {
AnimatedVisibility(
visible = zoomOut,
enter = expandIn(expandFrom = Alignment.Center, initialSize = { IntSize(it.width, 0) }),
exit = shrinkOut(shrinkTowards = Alignment.Center, targetSize = { IntSize(it.width, 0) })
) {
bottom()
}
}
},
measurePolicy = { measurables, constraints ->
val (contentMeasurable, bottomMeasurable) = measurables
val bottomPlaceable = bottomMeasurable.measure(constraints)
val contentPlaceable = contentMeasurable.measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight) {
bottomPlaceable.place(x = 0, y = constraints.maxHeight - bottomPlaceable.height)
contentPlaceable.placeWithLayer(0, 0) {
val height = constraints.maxHeight - 2 * bottomPlaceable.height
val scale = height / contentPlaceable.height.toFloat()
scaleX = scale
scaleY = scale
}
}
}
)
}

View File

@@ -0,0 +1,147 @@
@file:Suppress("MagicNumber")
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.components.ZoomOutLayout
import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.config.rememberPageCurlConfig
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
import eu.wewox.pagecurl.ui.SpacingLarge
import eu.wewox.pagecurl.ui.SpacingMedium
import eu.wewox.pagecurl.ui.SpacingSmall
/**
* Back-Page Configuration in Page Curl.
* Example how to customize the back-page (the back of the page user see during the drag or animation).
*/
@Composable
fun BackPagePageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.backPageHowToPages }
var zoomOut by remember { mutableStateOf(false) }
val state = rememberPageCurlState()
val config = rememberPageCurlConfig(
onCustomTap = { size, position ->
// When PageCurl is zoomed out then zoom back in
// Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl
if (zoomOut) {
zoomOut = false
true
} else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) {
zoomOut = true
true
} else {
false
}
}
)
ZoomOutLayout(
zoomOut = zoomOut,
config = config,
bottom = { SettingsRow(config) },
) {
PageCurl(
count = pages.size,
state = state,
config = config,
) { index ->
HowToPage(index, pages[index])
}
}
}
}
@Composable
private fun SettingsRow(
config: PageCurlConfig,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = SpacingLarge)
) {
Text(
text = "Alpha",
modifier = Modifier.padding(horizontal = SpacingLarge)
)
Slider(
value = config.backPageContentAlpha,
onValueChange = {
config.backPageContentAlpha = it
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = SpacingLarge)
)
Text(
text = "Color",
modifier = Modifier.padding(horizontal = SpacingLarge)
)
Row(
horizontalArrangement = Arrangement.spacedBy(SpacingMedium),
modifier = Modifier
.padding(top = SpacingSmall)
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(horizontal = SpacingLarge)
) {
backPageColors.forEach { color ->
Spacer(
modifier = Modifier
.size(64.dp)
.border(2.dp, color, CircleShape)
.background(color.copy(alpha = 0.8f), CircleShape)
.clip(CircleShape)
.clickable {
config.backPageColor = color
}
)
}
}
}
}
private val backPageColors: List<Color> = listOf(
Color(0xFFF9CEEE),
Color(0xFF68A7AD),
Color(0xFFE5CB9F),
Color(0xFFAC7D88),
Color(0xFF9ADCFF),
Color(0xFFFFF89A),
Color(0xFFCDB699),
Color(0xFFA267AC),
)

View File

@@ -0,0 +1,166 @@
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.components.ZoomOutLayout
import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.config.rememberPageCurlConfig
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
import eu.wewox.pagecurl.ui.SpacingLarge
import eu.wewox.pagecurl.ui.SpacingMedium
import eu.wewox.pagecurl.ui.SpacingSmall
/**
* Interactions Configurations In Page Curl.
* Example interactions (drag / tap) can be customized.
*/
@Composable
fun InteractionConfigInPageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.interactionSettingsHowToPages }
var zoomOut by remember { mutableStateOf(false) }
val state = rememberPageCurlState()
val config = rememberPageCurlConfig(
onCustomTap = { size, position ->
// When PageCurl is zoomed out then zoom back in
// Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl
if (zoomOut) {
zoomOut = false
true
} else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) {
zoomOut = true
true
} else {
false
}
}
)
ZoomOutLayout(
zoomOut = zoomOut,
config = config,
bottom = { SettingsRow(config) },
) {
PageCurl(
count = pages.size,
state = state,
config = config,
) { index ->
HowToPage(index, pages[index])
}
}
}
}
@Composable
private fun SettingsRow(
config: PageCurlConfig,
modifier: Modifier = Modifier
) {
var selectedOption by remember { mutableStateOf(InteractionOption.Drag) }
Column(
verticalArrangement = Arrangement.spacedBy(SpacingSmall),
modifier = modifier
.fillMaxWidth()
.padding(SpacingLarge)
) {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxWidth()
.selectableGroup()
) {
InteractionOption.values().forEach { option ->
Row(
Modifier
.selectable(
selected = selectedOption == option,
onClick = { selectedOption = option },
role = Role.RadioButton
),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = selectedOption == option,
onClick = null
)
Text(
text = option.name,
modifier = Modifier.padding(start = SpacingMedium)
)
}
}
}
SettingsRowSlider(
selectedOption = selectedOption,
config = config,
)
}
}
@Composable
private fun SettingsRowSlider(
selectedOption: InteractionOption,
config: PageCurlConfig,
) {
when (selectedOption) {
InteractionOption.Drag -> {
Slider(
value = config.dragForwardInteraction.start.left,
onValueChange = {
config.dragForwardInteraction = PageCurlConfig.DragInteraction(
Rect(it, 0.0f, 1.0f, 1.0f),
Rect(0.0f, 0.0f, it, 1.0f)
)
config.dragBackwardInteraction = PageCurlConfig.DragInteraction(
Rect(0.0f, 0.0f, it, 1.0f),
Rect(it, 0.0f, 1.0f, 1.0f),
)
},
modifier = Modifier.fillMaxWidth()
)
}
InteractionOption.Tap -> {
Slider(
value = config.tapForwardInteraction.target.left,
onValueChange = {
config.tapForwardInteraction = PageCurlConfig.TapInteraction(
Rect(it, 0.0f, 1.0f, 1.0f),
)
config.tapBackwardInteraction = PageCurlConfig.TapInteraction(
Rect(0.0f, 0.0f, it, 1.0f),
)
},
modifier = Modifier.fillMaxWidth()
)
}
}
}
private enum class InteractionOption { Drag, Tap }

View File

@@ -0,0 +1,123 @@
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.paging.LoadState.Loading
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import app.cash.paging.compose.collectAsLazyPagingItems
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
/**
* PageCurl with lazy paging.
* Example how component could be used with paging implementation.
*/
@Composable
fun PagingPageCurlScreen() {
val startPage = 4 // in range 0..9
val startPageOffset = 5 // in range 0..9
val items = rememberPager(startPage).collectAsLazyPagingItems()
val loading = with(items.loadState) { refresh is Loading || append is Loading || prepend is Loading }
Box(Modifier.fillMaxSize()) {
if (items.loadState.refresh is Loading) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
CircularProgressIndicator()
}
} else {
PageCurl(
count = items.itemCount,
key = { index -> items.peek(index).hashCode() },
state = rememberPageCurlState(initialCurrent = startPageOffset),
) { index ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
Text(items[index]?.content.orEmpty())
Text(if (loading) "Loading..." else "")
}
}
}
}
}
@Composable
private fun rememberPager(startPage: Int): Flow<PagingData<Item>> =
remember {
Pager(
config = PagingConfig(
pageSize = 10,
prefetchDistance = 3,
),
pagingSourceFactory = { ItemPagingSource(startPage, BackendService()) }
).flow
}
private class Item(val content: String)
private class Response(val items: List<Item>, val prePageNumber: Int, val nextPageNumber: Int)
private class BackendService {
suspend fun searchItems(page: Int): Response {
delay(1_000L)
return Response(
items = List(10) { Item("Content #${page * 10 + it}") },
prePageNumber = page - 1,
nextPageNumber = page + 1,
)
}
}
private class ItemPagingSource(
private val startPage: Int,
private val backend: BackendService
) : PagingSource<Int, Item>() {
@Suppress("TooGenericExceptionCaught") // It is only for demo purpose
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> =
try {
val nextPageNumber = params.key ?: startPage
val response = backend.searchItems(nextPageNumber)
LoadResult.Page(
data = response.items,
prevKey = response.prePageNumber.takeIf { it >= 0 },
nextKey = response.nextPageNumber.takeIf { it < 10 }
)
} catch (e: Exception) {
LoadResult.Error(e)
}
override fun getRefreshKey(state: PagingState<Int, Item>): Int? =
state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}

View File

@@ -0,0 +1,63 @@
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.components.SettingsPopup
import eu.wewox.pagecurl.config.rememberPageCurlConfig
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
/**
* Page Curl With Settings.
* Showcases how individual interactions can be toggled on / off.
*/
@Composable
fun SettingsPageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.interactionHowToPages }
var showPopup by rememberSaveable { mutableStateOf(false) }
val state = rememberPageCurlState()
val config = rememberPageCurlConfig(
onCustomTap = { size, position ->
// Detect tap somewhere in the center with 64 radius and show popup
if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) {
showPopup = true
true
} else {
false
}
}
)
PageCurl(
count = pages.size,
state = state,
config = config,
) { index ->
HowToPage(index, pages[index])
}
if (showPopup) {
SettingsPopup(
config = config,
onDismiss = {
showPopup = false
}
)
}
}
}

View File

@@ -0,0 +1,112 @@
@file:Suppress("MagicNumber")
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.components.ZoomOutLayout
import eu.wewox.pagecurl.config.PageCurlConfig
import eu.wewox.pagecurl.config.rememberPageCurlConfig
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
import eu.wewox.pagecurl.ui.SpacingLarge
/**
* Shadow Configuration in Page Curl.
* Example how to customize shadow of the page.
*/
@Composable
fun ShadowInPageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.shadowHowToPages }
var zoomOut by remember { mutableStateOf(false) }
val state = rememberPageCurlState()
val config = rememberPageCurlConfig(
onCustomTap = { size, position ->
// When PageCurl is zoomed out then zoom back in
// Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl
if (zoomOut) {
zoomOut = false
true
} else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) {
zoomOut = true
true
} else {
false
}
}
)
ZoomOutLayout(
zoomOut = zoomOut,
config = config,
bottom = { SettingsRow(config) },
) {
PageCurl(
count = pages.size,
state = state,
config = config,
) { index ->
HowToPage(index, pages[index])
}
}
}
}
@Composable
private fun SettingsRow(
config: PageCurlConfig,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(SpacingLarge)
) {
Text(text = "Alpha")
Slider(
value = config.shadowAlpha,
onValueChange = {
config.shadowAlpha = it
},
modifier = Modifier.fillMaxWidth()
)
Text(text = "Radius")
Slider(
value = config.shadowRadius.value,
onValueChange = {
config.shadowRadius = it.dp
},
valueRange = 0f..32f,
modifier = Modifier.fillMaxWidth()
)
Text(text = "Horizontal offset")
Slider(
value = config.shadowOffset.x.value,
onValueChange = {
config.shadowOffset = DpOffset(it.dp, 0.dp)
},
valueRange = -20f..20f,
modifier = Modifier.fillMaxWidth()
)
}
}

View File

@@ -0,0 +1,25 @@
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.page.PageCurl
/**
* Simple Page Curl.
* Basic PageCurl usage.
*/
@Composable
fun SimplePageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.simpleHowToPages }
PageCurl(count = pages.size) { index ->
HowToPage(index, pages[index])
}
}
}

View File

@@ -0,0 +1,129 @@
package eu.wewox.pagecurl.screens
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toOffset
import eu.wewox.pagecurl.HowToPageData
import eu.wewox.pagecurl.components.HowToPage
import eu.wewox.pagecurl.components.ZoomOutLayout
import eu.wewox.pagecurl.config.rememberPageCurlConfig
import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.PageCurlState
import eu.wewox.pagecurl.page.rememberPageCurlState
import eu.wewox.pagecurl.ui.SpacingLarge
import eu.wewox.pagecurl.ui.SpacingMedium
import kotlinx.coroutines.launch
/**
* Page Curl With State Management.
* Example how state can be used to change current page (snap / animate).
*/
@Composable
fun StateInPageCurlScreen() {
Box(Modifier.fillMaxSize()) {
val pages = remember { HowToPageData.stateHowToPages }
var zoomOut by remember { mutableStateOf(false) }
val state = rememberPageCurlState()
val config = rememberPageCurlConfig(
onCustomTap = { size, position ->
// When PageCurl is zoomed out then zoom back in
// Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl
if (zoomOut) {
zoomOut = false
true
} else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) {
zoomOut = true
true
} else {
false
}
}
)
ZoomOutLayout(
zoomOut = zoomOut,
config = config,
bottom = { SettingsRow(pages.size, state) },
) {
PageCurl(
count = pages.size,
state = state,
config = config,
) { index ->
HowToPage(index, pages[index])
}
}
}
}
@Composable
private fun SettingsRow(
max: Int,
state: PageCurlState,
modifier: Modifier = Modifier
) {
Row(
horizontalArrangement = Arrangement.spacedBy(SpacingMedium, Alignment.CenterHorizontally),
modifier = modifier
.horizontalScroll(rememberScrollState())
.fillMaxWidth()
.padding(SpacingLarge)
) {
SettingsRowButton("Snap to first") {
state.snapTo(0)
}
SettingsRowButton("Snap to last") {
state.snapTo(max)
}
SettingsRowButton("Snap forward") {
state.snapTo(state.current + 1)
}
SettingsRowButton("Snap backward") {
state.snapTo(state.current - 1)
}
SettingsRowButton("Animate forward") {
state.next()
}
SettingsRowButton("Animate backward") {
state.prev()
}
}
}
@Composable
private fun SettingsRowButton(
text: String,
modifier: Modifier = Modifier,
onClick: suspend () -> Unit,
) {
val scope = rememberCoroutineScope()
Button(
onClick = { scope.launch { onClick() } },
modifier = modifier
) {
Text(text = text)
}
}

View File

@@ -0,0 +1,18 @@
package eu.wewox.pagecurl.ui
import androidx.compose.ui.unit.dp
/**
* The small spacing.
*/
val SpacingSmall = 8.dp
/**
* The medium spacing.
*/
val SpacingMedium = 16.dp
/**
* The large spacing.
*/
val SpacingLarge = 32.dp

View File

@@ -0,0 +1,8 @@
@file:Suppress("UndocumentedPublicProperty")
package eu.wewox.pagecurl.ui.theme
import androidx.compose.ui.graphics.Color
val LightBlue = Color(0xFF6DD3FF)
val LightYellow = Color(0xFFFFF281)

View File

@@ -0,0 +1,61 @@
package eu.wewox.pagecurl.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorScheme = darkColorScheme(
primary = LightBlue,
secondary = LightBlue,
tertiary = LightYellow,
onPrimary = Color.Black,
onSecondary = Color.Black,
onTertiary = Color.Black,
surface = Color.Black,
background = Color.Black,
onSurface = Color.White,
onBackground = Color.White,
)
private val LightColorScheme = lightColorScheme(
primary = LightBlue,
secondary = LightBlue,
tertiary = LightYellow,
onPrimary = Color.Black,
onSecondary = Color.Black,
onTertiary = Color.Black,
surface = Color.White,
background = Color.White,
onSurface = Color.Black,
onBackground = Color.Black,
)
/**
* The theme to use for demo application.
*/
@Composable
fun PageCurlTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography(),
content = content
)
}

View File

@@ -0,0 +1,13 @@
package eu.wewox.pagecurl
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import eu.wewox.pagecurl.components.TopBar
@Composable
internal actual fun BackablePageScreen(content: @Composable () -> Unit) {
Box {
content()
TopBar(title = "", onBackClick = { PageStateHandler.navToMain() })
}
}

View File

@@ -0,0 +1,6 @@
@file:Suppress("unused")
import androidx.compose.ui.window.ComposeUIViewController
import eu.wewox.pagecurl.App
fun createUIViewController() = ComposeUIViewController { App() }

View File

@@ -0,0 +1,13 @@
package eu.wewox.pagecurl
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import eu.wewox.pagecurl.components.TopBar
@Composable
internal actual fun BackablePageScreen(content: @Composable () -> Unit) {
Box {
content()
TopBar(title = "", onBackClick = { PageStateHandler.navToMain() })
}
}