mirror of
https://github.com/fankes/pagecurl-multiplatform.git
synced 2025-09-08 03:24:03 +08:00
feat: support compose multiplatform
This commit is contained in:
1
samples/shared/.gitignore
vendored
Normal file
1
samples/shared/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
87
samples/shared/build.gradle.kts
Normal file
87
samples/shared/build.gradle.kts
Normal 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
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package eu.wewox.pagecurl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
internal actual fun BackablePageScreen(content: @Composable () -> Unit) {
|
||||
content()
|
||||
}
|
@@ -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)
|
@@ -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)"
|
||||
),
|
||||
}
|
@@ -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",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
@@ -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,
|
||||
)
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@@ -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),
|
||||
)
|
@@ -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 }
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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)
|
@@ -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
|
||||
)
|
||||
}
|
@@ -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() })
|
||||
}
|
||||
}
|
6
samples/shared/src/iosMain/kotlin/Main.ios.kt
Normal file
6
samples/shared/src/iosMain/kotlin/Main.ios.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import eu.wewox.pagecurl.App
|
||||
|
||||
fun createUIViewController() = ComposeUIViewController { App() }
|
@@ -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() })
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user