Update all dependencies

This commit is contained in:
Syer10
2023-12-01 19:21:07 -05:00
parent 30f43412d4
commit 2c088a3a7e
40 changed files with 385 additions and 431 deletions

View File

@@ -82,7 +82,7 @@ dependencies {
// Utility // Utility
implementation(libs.dateTime) implementation(libs.dateTime)
implementation(libs.immutableCollections) implementation(libs.immutableCollections)
implementation(libs.kds) implementation(libs.korge.foundation)
// Localization // Localization
implementation(libs.moko.core) implementation(libs.moko.core)
@@ -90,7 +90,7 @@ dependencies {
// Testing // Testing
testImplementation(kotlin("test-junit")) testImplementation(kotlin("test-junit"))
testImplementation(libs.compose.ui.test.junit4) //testImplementation(libs.compose.ui.test.junit4)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
} }

View File

@@ -50,14 +50,16 @@ subprojects {
if (name.contains("android", true)) { if (name.contains("android", true)) {
jvmTarget = Config.androidJvmTarget.toString() jvmTarget = Config.androidJvmTarget.toString()
} }
freeCompilerArgs += listOf("-Xexpect-actual-classes")
if (project.hasProperty("generateComposeCompilerMetrics")) { if (project.hasProperty("generateComposeCompilerMetrics")) {
freeCompilerArgs = freeCompilerArgs + listOf( freeCompilerArgs = freeCompilerArgs + listOf(
"-P", "-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.buildDir.absolutePath + "/compose_metrics", project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
"-P", "-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
project.buildDir.absolutePath + "/compose_metrics" project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath
) )
} }
} }
@@ -72,7 +74,7 @@ subprojects {
} }
plugins.withType<com.android.build.gradle.BasePlugin> { plugins.withType<com.android.build.gradle.BasePlugin> {
configure<com.android.build.gradle.BaseExtension> { configure<com.android.build.gradle.BaseExtension> {
compileSdkVersion(33) compileSdkVersion(34)
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 21
targetSdk = 31 targetSdk = 31
@@ -126,9 +128,8 @@ subprojects {
} }
} }
plugins.withType<de.jensklingenberg.ktorfit.gradle.KtorfitGradleSubPlugin> { plugins.withType<de.jensklingenberg.ktorfit.gradle.KtorfitGradlePlugin> {
configure<de.jensklingenberg.ktorfit.gradle.KtorfitGradleConfiguration> { configure<de.jensklingenberg.ktorfit.gradle.KtorfitGradleConfiguration> {
version = libs.versions.ktorfit.get()
logging = project.hasProperty("debugApp") logging = project.hasProperty("debugApp")
} }
} }
@@ -140,7 +141,7 @@ subprojects {
} }
plugins.withType<ComposePlugin> { plugins.withType<ComposePlugin> {
configure<ComposeExtension> { configure<ComposeExtension> {
kotlinCompilerPlugin.set(libs.versions.composeCompiler.get()) // kotlinCompilerPlugin.set(libs.versions.composeCompiler.get())
} }
} }
afterEvaluate { afterEvaluate {

View File

@@ -12,5 +12,5 @@ dependencies {
implementation(gradleKotlinDsl()) implementation(gradleKotlinDsl())
implementation(gradleApi()) implementation(gradleApi())
implementation(localGroovy()) implementation(localGroovy())
implementation("de.undercouch:gradle-download-task:5.3.0") implementation(libs.gradle.download.task)
} }

View File

@@ -1,2 +1,11 @@
rootProject.name = "buildSrc" rootProject.name = "buildSrc"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@@ -10,7 +10,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -28,6 +28,21 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("jvm") {
withAndroidTarget()
withJvm()
}
group("ios") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
all { all {
languageSettings { languageSettings {
@@ -53,7 +68,7 @@ kotlin {
api(libs.multiplatformSettings.coroutines) api(libs.multiplatformSettings.coroutines)
api(libs.multiplatformSettings.serialization) api(libs.multiplatformSettings.serialization)
api(libs.dateTime) api(libs.dateTime)
api(libs.kds) api(libs.korge.foundation)
api(compose("org.jetbrains.compose.ui:ui-text")) api(compose("org.jetbrains.compose.ui:ui-text"))
} }
} }
@@ -64,53 +79,28 @@ kotlin {
} }
} }
val jvmMain by creating { val jvmMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
} }
} }
val jvmTest by creating { val jvmTest by getting {
dependsOn(commonTest)
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val desktopMain by getting { val desktopMain by getting {
dependsOn(jvmMain)
dependencies { dependencies {
api(libs.appDirs) api(libs.appDirs)
} }
} }
val desktopTest by getting { val desktopTest by getting {
dependsOn(jvmTest)
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmMain)
dependencies {
api(libs.compose.ui.text)
}
} }
val androidUnitTest by getting { val androidUnitTest by getting {
dependsOn(jvmTest)
}
val iosMain by creating {
dependsOn(commonMain)
}
val iosTest by creating {
dependsOn(commonTest)
}
listOf(
"iosX64",
"iosArm64",
"iosSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(iosMain)
getByName(it + "Test").dependsOn(iosTest)
} }
} }
} }

View File

@@ -10,7 +10,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -28,6 +28,21 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("jvm") {
withAndroidTarget()
withJvm()
}
group("ios") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
all { all {
languageSettings { languageSettings {
@@ -58,47 +73,30 @@ kotlin {
} }
} }
val jvmMain by creating { val jvmMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
} }
} }
val jvmTest by creating { val jvmTest by getting {
dependsOn(commonTest)
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val desktopMain by getting { val desktopMain by getting {
dependsOn(jvmMain)
} }
val desktopTest by getting { val desktopTest by getting {
dependsOn(jvmTest)
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmMain)
} }
val androidUnitTest by getting { val androidUnitTest by getting {
dependsOn(jvmTest)
} }
val iosMain by creating { val iosMain by getting {
dependsOn(commonMain)
} }
val iosTest by creating { val iosTest by getting {
dependsOn(commonTest)
}
listOf(
"iosX64",
"iosArm64",
"iosSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(iosMain)
getByName(it + "Test").dependsOn(iosTest)
} }
} }
} }

View File

@@ -87,7 +87,7 @@ dependencies {
// Utility // Utility
implementation(libs.dateTime) implementation(libs.dateTime)
implementation(libs.immutableCollections) implementation(libs.immutableCollections)
implementation(libs.kds) implementation(libs.korge.foundation)
// Localization // Localization
implementation(libs.moko.core) implementation(libs.moko.core)

View File

@@ -11,7 +11,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -29,6 +29,21 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("jvm") {
withAndroidTarget()
withJvm()
}
group("ios") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
all { all {
languageSettings { languageSettings {
@@ -66,51 +81,34 @@ kotlin {
} }
} }
val jvmMain by creating { val jvmMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
api(libs.ktor.okHttp) api(libs.ktor.okHttp)
} }
} }
val jvmTest by creating { val jvmTest by getting {
dependsOn(commonTest)
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val desktopMain by getting { val desktopMain by getting {
dependsOn(jvmMain)
} }
val desktopTest by getting { val desktopTest by getting {
dependsOn(jvmTest)
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmMain)
} }
val androidUnitTest by getting { val androidUnitTest by getting {
dependsOn(jvmTest)
} }
val iosMain by creating { val iosMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(libs.ktor.darwin) api(libs.ktor.darwin)
} }
} }
val iosTest by creating { val iosTest by getting {
dependsOn(commonTest)
}
listOf(
"iosX64",
"iosArm64",
"iosSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(iosMain)
getByName(it + "Test").dependsOn(iosTest)
} }
} }
} }

View File

@@ -21,6 +21,7 @@ import io.ktor.client.plugins.auth.providers.DigestAuthCredentials
import io.ktor.client.plugins.auth.providers.basic import io.ktor.client.plugins.auth.providers.basic
import io.ktor.client.plugins.auth.providers.digest import io.ktor.client.plugins.auth.providers.digest
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging import io.ktor.client.plugins.logging.Logging
@@ -49,6 +50,10 @@ fun httpClient(
expectSuccess = true expectSuccess = true
defaultRequest {
url(serverPreferences.serverUrl().get().toString())
}
engine { engine {
proxy = when (serverPreferences.proxy().get()) { proxy = when (serverPreferences.proxy().get()) {
Proxy.NO_PROXY -> null Proxy.NO_PROXY -> null

View File

@@ -4,6 +4,5 @@ android.enableJetifier=false
android.useAndroidX=true android.useAndroidX=true
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true
kotlin.native.ignoreDisabledTargets=true kotlin.native.ignoreDisabledTargets=true
kotlin.native.cacheKind=none
org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true
kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidSourceSetLayoutVersion=2

View File

@@ -1,82 +1,76 @@
[versions] [versions]
# Kotlin # Kotlin
kotlin = "1.8.22" kotlin = "1.9.21"
coroutines = "1.7.2" coroutines = "1.7.3"
# Serialization # Serialization
json = "1.5.1" json = "1.6.2"
# Compose # Compose
composeGradle = "1.4.1" composeGradle = "1.5.11"
composeCompiler = "1.4.8"
composeAndroidRuntime = "1.4.3"
composeAndroidFoundation = "1.4.3"
composeAndroidUI = "1.4.3"
composeAndroidAnimation = "1.4.3"
composeAndroidMaterial = "1.4.3"
# Compose Libraries # Compose Libraries
voyager = "1.0.0-rc06" voyager = "1.0.0-rc10"
accompanist = "0.30.1" accompanist = "0.30.1"
googleAccompanist = "0.30.1" googleAccompanist = "0.30.1"
imageloader = "1.5.3" imageloader = "1.7.1"
materialDialogs = "0.9.3" materialDialogs = "0.9.4"
# Android # Android
androidGradle = "8.0.2" androidGradle = "8.1.4"
core = "1.9.0" core = "1.12.0"
appCompat = "1.7.0-alpha02" appCompat = "1.7.0-alpha03"
activityCompose = "1.7.2" activityCompose = "1.8.1"
work = "2.8.1" work = "2.9.0"
# Android Lifecycle # Android Lifecycle
lifecycle = "2.6.1" lifecycle = "2.6.2"
# Swing # Swing
darklaf = "3.0.2" darklaf = "3.0.2"
# Ksp # Ksp
ksp = "1.8.22-1.0.11" ksp = "1.9.21-1.0.15"
# Dependency Injection # Dependency Injection
kotlinInject = "0.6.1" kotlinInject = "0.6.3"
# Network # Network
ktor = "2.3.2" ktor = "2.3.6"
ktorfit = "1.4.2" ktorfit = "1.10.2"
ktorfitCompiler = "1.0.0"
# Logging # Logging
slf4j = "2.0.7" slf4j = "2.0.9"
log4j = "2.20.0" log4j = "2.22.0"
kmlogging = "1.3.0" kmlogging = "1.3.0"
# Storage # Storage
okio = "3.3.0" okio = "3.6.0"
appDirs = "1.2.1" appDirs = "1.2.1"
# Preferences # Preferences
multiplatformSettings = "1.0.0-alpha01" multiplatformSettings = "1.0.0-alpha01"
# Utility # Utility
desugarJdkLibs = "2.0.3" desugarJdkLibs = "2.0.4"
aboutLibraries = "10.8.0" aboutLibraries = "10.9.2"
dateTime = "0.4.0" dateTime = "0.5.0"
immutableCollections = "0.3.5" immutableCollections = "0.3.6"
kds = "4.0.7" korge = "5.1.0"
gradleDownloadTask = "5.4.0"
# Localization # Localization
moko = "0.23.0" moko = "0.23.0"
# BuildConfigs # BuildConfigs
buildconfig = "4.1.1" buildconfig = "4.2.0"
buildkonfig = "0.13.3" buildkonfig = "0.15.1"
# Linter # Linter
kotlinter = "3.15.0" kotlinter = "4.1.0"
# Version updates # Version updates
versions = "0.47.0" versions = "0.50.0"
# Optimizer # Optimizer
proguard = "7.3.2" proguard = "7.3.2"
@@ -92,16 +86,6 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
serialization-json-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "json" } serialization-json-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "json" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "json" } serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "json" }
# Compose
compose-animation = { module = "androidx.compose.animation:animation", version.ref = "composeAndroidAnimation" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "composeAndroidFoundation" }
compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "composeAndroidRuntime" }
compose-ui-core = { module = "androidx.compose.ui:ui", version.ref = "composeAndroidUI" }
compose-ui-util = { module = "androidx.compose.ui:ui-util", version.ref = "composeAndroidUI" }
compose-ui-text = { module = "androidx.compose.ui:ui-text", version.ref = "composeAndroidUI" }
compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeAndroidUI" }
compose-material-core = { module = "androidx.compose.material:material", version.ref = "composeAndroidMaterial" }
compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "composeAndroidMaterial" }
# Compose UI # Compose UI
voyager-core = { module = "cafe.adriel.voyager:voyager-core", version.ref = "voyager" } voyager-core = { module = "cafe.adriel.voyager:voyager-core", version.ref = "voyager" }
voyager-navigation = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-navigation = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
@@ -168,7 +152,7 @@ aboutLibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref
aboutLibraries-ui = { module = "com.mikepenz:aboutlibraries-compose", version.ref = "aboutLibraries" } aboutLibraries-ui = { module = "com.mikepenz:aboutlibraries-compose", version.ref = "aboutLibraries" }
dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "dateTime" } dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "dateTime" }
immutableCollections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "immutableCollections" } immutableCollections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "immutableCollections" }
kds = { module = "com.soywiz.korlibs.kds:kds", version.ref = "kds" } korge-foundation = { module = "com.soywiz.korge:korge-foundation", version.ref = "korge" }
# Localization # Localization
moko-core = { module = "dev.icerock.moko:resources", version.ref = "moko" } moko-core = { module = "dev.icerock.moko:resources", version.ref = "moko" }
@@ -177,6 +161,9 @@ moko-compose = { module = "dev.icerock.moko:resources-compose", version.ref = "m
# Optimizer # Optimizer
proguard = { module = "com.guardsquare:proguard-gradle", version.ref = "proguard" } proguard = { module = "com.guardsquare:proguard-gradle", version.ref = "proguard" }
# Gradle
gradle-download-task = { module = "de.undercouch:gradle-download-task", version.ref = "gradleDownloadTask" }
[plugins] [plugins]
# Kotlin # Kotlin
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
@@ -195,7 +182,7 @@ compose = { id = "org.jetbrains.compose", version.ref = "composeGradle" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
# Network # Network
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfitCompiler" } ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }
# Localization # Localization
moko-gradle = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" } moko-gradle = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
@@ -215,11 +202,4 @@ aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "abo
[bundles] [bundles]
compose-android = [ compose-android = [
"compose-animation",
"compose-foundation",
"compose-runtime",
"compose-ui-core",
"compose-ui-util",
"compose-material-core",
"compose-material-icons"
] ]

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

5
gradlew vendored
View File

@@ -130,11 +130,14 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then

View File

@@ -6,7 +6,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -31,6 +31,8 @@ kotlin {
iosArm64(configure = configuration) iosArm64(configure = configuration)
iosSimulatorArm64(configure = configuration) iosSimulatorArm64(configure = configuration)
applyDefaultHierarchyTemplate()
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
@@ -44,6 +46,14 @@ kotlin {
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
} }
} }
getByName("desktopMain") {
dependsOn(commonMain)
}
getByName("androidMain") {
dependsOn(commonMain)
}
} }
} }
@@ -65,7 +75,7 @@ android {
} }
sourceSets.getByName("main") { sourceSets.getByName("main") {
assets.srcDir(File(buildDir, "generated/moko/androidMain/assets")) assets.srcDir(File(layout.buildDirectory.asFile.get(), "generated/moko/androidMain/assets"))
res.srcDir(File(buildDir, "generated/moko/androidMain/res")) res.srcDir(File(layout.buildDirectory.asFile.get(), "generated/moko/androidMain/res"))
} }
} }

View File

@@ -1,9 +1,5 @@
import org.jetbrains.compose.compose import org.jetbrains.compose.compose
import org.jetbrains.compose.experimental.uikit.tasks.ExperimentalPackComposeApplicationForXCodeTask
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import java.io.File
import kotlin.reflect.full.declaredMemberProperties
@Suppress("DSL_SCOPE_VIOLATION") @Suppress("DSL_SCOPE_VIOLATION")
plugins { plugins {
@@ -17,28 +13,28 @@ plugins {
kotlin { kotlin {
val configuration: KotlinNativeTarget.() -> Unit = { val configuration: KotlinNativeTarget.() -> Unit = {
binaries { binaries.framework {
executable { baseName = "ios"
entryPoint = "ca.gosyer.jui.ios.main" isStatic = true
freeCompilerArgs = freeCompilerArgs + listOf(
"-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreGraphics"
)
// TODO: the current compose binary surprises LLVM, so disable checks for now.
freeCompilerArgs = freeCompilerArgs + "-Xdisable-phases=VerifyBitcode"
}
} }
} }
iosX64("uikitX64", configuration) iosX64("uikitX64", configuration)
iosArm64("uikitArm64", configuration) iosArm64("uikitArm64", configuration)
iosSimulatorArm64("uikitSimulatorArm64", configuration) iosSimulatorArm64("uikitSimulatorArm64", configuration)
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("uikit") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
val commonMain by getting val uikitMain by getting {
val commonTest by getting
val uikitMain by creating {
dependsOn(commonMain)
dependencies { dependencies {
implementation(projects.core) implementation(projects.core)
implementation(projects.i18n) implementation(projects.i18n)
@@ -100,7 +96,7 @@ kotlin {
// Utility // Utility
implementation(libs.dateTime) implementation(libs.dateTime)
implementation(libs.immutableCollections) implementation(libs.immutableCollections)
implementation(libs.kds) implementation(libs.korge.foundation)
// Localization // Localization
implementation(libs.moko.core) implementation(libs.moko.core)
@@ -112,30 +108,7 @@ kotlin {
testImplementation(libs.coroutines.test)*/ testImplementation(libs.coroutines.test)*/
} }
} }
val uikitTest by creating { val uikitTest by getting {
dependsOn(commonTest)
}
listOf(
"uikitX64",
"uikitArm64",
"uikitSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(uikitMain)
getByName(it + "Test").dependsOn(uikitTest)
}
}
}
compose.experimental {
uikit.application {
bundleIdPrefix = "ca.gosyer.jui.app.ios"
projectName = "Tachidesk-JUI"
// ./gradlew :app:ios:iosDeployIPhone13Debug
deployConfigurations {
simulator("IPhone13") {
device = org.jetbrains.compose.experimental.dsl.IOSDevices.IPHONE_13
}
} }
} }
} }
@@ -151,92 +124,6 @@ dependencies {
} }
} }
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.all {
// TODO: the current compose binary surprises LLVM, so disable checks for now.
freeCompilerArgs = freeCompilerArgs + "-Xdisable-phases=VerifyBitcode"
}
}
}
buildkonfig { buildkonfig {
packageName = "ca.gosyer.jui.ios.build" packageName = "ca.gosyer.jui.ios.build"
} }
// todo: Remove when resolved: https://github.com/icerockdev/moko-resources/issues/372
// copy .bundle from all .klib to .kexe
tasks.withType<KotlinNativeLink>()
.configureEach {
val linkTask: KotlinNativeLink = this
val outputDir: File = this.outputFile.get().parentFile
@Suppress("ObjectLiteralToLambda") // lambda broke up-to-date
val action = object : Action<Task> {
override fun execute(t: Task) {
(linkTask.libraries + linkTask.sources)
.filter { library -> library.extension == "klib" }
.filter(File::exists)
.forEach { inputFile ->
val klibKonan = org.jetbrains.kotlin.konan.file.File(inputFile.path)
val klib = org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl(
klib = klibKonan,
component = "default"
)
val layout = klib.extractingToTemp
// extracting bundles
layout
.resourcesDir
.absolutePath
.let(::File)
.listFiles { file: File -> file.extension == "bundle" }
// copying bundles to app
?.forEach {
logger.info("${it.absolutePath} copying to $outputDir")
it.copyRecursively(
target = File(outputDir, it.name),
overwrite = true
)
}
}
}
}
doLast(action)
}
// copy .bundle from .kexe to .app
tasks.withType<ExperimentalPackComposeApplicationForXCodeTask>()
.configureEach {
val packTask: ExperimentalPackComposeApplicationForXCodeTask = this
val kclass = ExperimentalPackComposeApplicationForXCodeTask::class
val kotlinBinaryField =
kclass.declaredMemberProperties.single { it.name == "kotlinBinary" }
val destinationDirField =
kclass.declaredMemberProperties.single { it.name == "destinationDir" }
val executablePathField =
kclass.declaredMemberProperties.single { it.name == "executablePath" }
@Suppress("ObjectLiteralToLambda") // lambda broke up-to-date
val action = object : Action<Task> {
override fun execute(t: Task) {
val kotlinBinary: RegularFile =
(kotlinBinaryField.get(packTask) as RegularFileProperty).get()
val destinationDir: Directory =
(destinationDirField.get(packTask) as DirectoryProperty).get()
val executablePath: String =
(executablePathField.get(packTask) as Provider<String>).get()
val outputDir: File = File(destinationDir.asFile, executablePath).parentFile
val bundleSearchDir: File = kotlinBinary.asFile.parentFile
bundleSearchDir
.listFiles { file: File -> file.extension == "bundle" }
?.forEach { file ->
file.copyRecursively(File(outputDir, file.name), true)
}
}
}
doLast(action)
}

View File

@@ -11,7 +11,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -29,6 +29,21 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("jvm") {
withAndroidTarget()
withJvm()
}
group("ios") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
all { all {
languageSettings { languageSettings {
@@ -78,33 +93,28 @@ kotlin {
} }
} }
val jvmMain by creating { val jvmMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
api(compose.desktop.currentOs) api(compose.desktop.currentOs)
} }
} }
val jvmTest by creating { val jvmTest by getting {
dependsOn(commonTest)
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val desktopMain by getting { val desktopMain by getting {
dependsOn(jvmMain)
dependencies { dependencies {
api(libs.coroutines.swing) api(libs.coroutines.swing)
} }
} }
val desktopTest by getting { val desktopTest by getting {
dependsOn(jvmTest)
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmMain)
dependencies { dependencies {
api(libs.bundles.compose.android) api(libs.bundles.compose.android)
api(libs.androidx.core) api(libs.androidx.core)
@@ -114,23 +124,11 @@ kotlin {
} }
} }
val androidUnitTest by getting { val androidUnitTest by getting {
dependsOn(jvmTest)
} }
val iosMain by creating { val iosMain by getting {
dependsOn(commonMain)
} }
val iosTest by creating { val iosTest by getting {
dependsOn(commonTest)
}
listOf(
"iosX64",
"iosArm64",
"iosSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(iosMain)
getByName(it + "Test").dependsOn(iosTest)
} }
} }
} }

View File

@@ -9,20 +9,22 @@ package ca.gosyer.jui.ui.base.image
import android.os.Build import android.os.Build
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.Bitmap
import com.seiko.imageloader.BitmapConfig
import com.seiko.imageloader.cache.disk.DiskCacheBuilder import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryKey
import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.component.ComponentRegistryBuilder
import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.component.setupDefaultComponents
import com.seiko.imageloader.option.Options
import com.seiko.imageloader.option.OptionsBuilder import com.seiko.imageloader.option.OptionsBuilder
import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.option.androidContext
import okio.Path.Companion.toOkioPath import okio.Path.Companion.toOkioPath
actual fun OptionsBuilder.configure(contextWrapper: ContextWrapper) { actual fun OptionsBuilder.configure(contextWrapper: ContextWrapper) {
imageConfig = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { bitmapConfig = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Options.ImageConfig.ARGB_8888 BitmapConfig.ARGB_8888
} else { } else {
Options.ImageConfig.HARDWARE BitmapConfig.HARDWARE
} }
androidContext(contextWrapper) androidContext(contextWrapper)
} }
@@ -42,5 +44,5 @@ actual fun DiskCacheBuilder.configure(
maxSizeBytes(1024 * 1024 * 150) // 150 MB maxSizeBytes(1024 * 1024 * 150) // 150 MB
} }
actual fun MemoryCacheBuilder.configure(contextWrapper: ContextWrapper) { actual fun MemoryCacheBuilder<MemoryKey, Bitmap>.configure(contextWrapper: ContextWrapper) {
} }

View File

@@ -35,10 +35,10 @@ fun getMaterialDialogProperties(
dismissOnClickOutside = dismissOnClickOutside, dismissOnClickOutside = dismissOnClickOutside,
securePolicy = securePolicy, securePolicy = securePolicy,
usePlatformDefaultWidth = usePlatformDefaultWidth, usePlatformDefaultWidth = usePlatformDefaultWidth,
position = position, windowPosition = position,
size = size, windowSize = size,
title = title, windowTitle = title,
icon = icon, windowIcon = icon,
resizable = resizable, windowIsResizable = resizable,
) )
} }

View File

@@ -13,9 +13,11 @@ import ca.gosyer.jui.domain.server.service.ServerPreferences
import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.ui.base.ImageCache import ca.gosyer.jui.ui.base.ImageCache
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.Bitmap
import com.seiko.imageloader.ImageLoader import com.seiko.imageloader.ImageLoader
import com.seiko.imageloader.cache.disk.DiskCacheBuilder import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryKey
import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.component.ComponentRegistryBuilder
import com.seiko.imageloader.component.fetcher.MokoResourceFetcher import com.seiko.imageloader.component.fetcher.MokoResourceFetcher
import com.seiko.imageloader.component.keyer.Keyer import com.seiko.imageloader.component.keyer.Keyer
@@ -138,4 +140,4 @@ expect fun DiskCacheBuilder.configure(
cacheDir: String, cacheDir: String,
) )
expect fun MemoryCacheBuilder.configure(contextWrapper: ContextWrapper) expect fun MemoryCacheBuilder<MemoryKey, Bitmap>.configure(contextWrapper: ContextWrapper)

View File

@@ -27,7 +27,6 @@ import ca.gosyer.jui.ui.base.state.getStateFlow
import ca.gosyer.jui.ui.util.lang.CollatorComparator import ca.gosyer.jui.ui.util.lang.CollatorComparator
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import ca.gosyer.jui.uicore.vm.ViewModel import ca.gosyer.jui.uicore.vm.ViewModel
import cafe.adriel.voyager.core.model.coroutineScope
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -198,7 +197,7 @@ class LibraryScreenViewModel
log.warn(it) { "Failed to get manga list from category ${category.name}" } log.warn(it) { "Failed to get manga list from category ${category.name}" }
library.mangaMap.setError(category.id, it) library.mangaMap.setError(category.id, it)
} }
.launchIn(coroutineScope) .launchIn(scope)
} }
} }
.catch { .catch {

View File

@@ -37,7 +37,7 @@ fun LibraryPager(
) { ) {
if (categories.isEmpty()) return if (categories.isEmpty()) return
HorizontalPager(categories.size, state = pagerState) { HorizontalPager(state = pagerState) {
when (val library = getLibraryForPage(categories[it].id).value) { when (val library = getLibraryForPage(categories[it].id).value) {
CategoryState.Loading -> LoadingScreen() CategoryState.Loading -> LoadingScreen()
is CategoryState.Failed -> ErrorScreen(library.e.message) is CategoryState.Failed -> ErrorScreen(library.e.message)

View File

@@ -12,7 +12,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -90,7 +89,9 @@ fun LibraryScreenContent(
} }
BoxWithConstraints { BoxWithConstraints {
val pagerState = rememberPagerState(selectedCategoryIndex) val pagerState = rememberPagerState(selectedCategoryIndex) {
(libraryState as? LibraryState.Loaded)?.categories?.size ?: 1
}
LaunchedEffect(pagerState.isScrollInProgress to pagerState.currentPage) { LaunchedEffect(pagerState.isScrollInProgress to pagerState.currentPage) {
if (!pagerState.isScrollInProgress && pagerState.currentPage != selectedCategoryIndex) { if (!pagerState.isScrollInProgress && pagerState.currentPage != selectedCategoryIndex) {
onPageChanged(pagerState.currentPage) onPageChanged(pagerState.currentPage)
@@ -240,11 +241,7 @@ fun WideLibraryScreenContent(
if (showingMenu) { if (showingMenu) {
Box( Box(
Modifier.fillMaxSize().pointerInput(Unit) { Modifier.fillMaxSize().pointerInput(Unit) {
forEachGesture { detectTapGestures(onTap = { setShowingMenu(false) })
detectTapGestures {
setShowingMenu(false)
}
}
}, },
) )
} }
@@ -295,7 +292,7 @@ fun ThinLibraryScreenContent(
) { ) {
val bottomSheetState = rememberModalBottomSheetState( val bottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden, ModalBottomSheetValue.Hidden,
confirmStateChange = { confirmValueChange = {
when (it) { when (it) {
ModalBottomSheetValue.Hidden -> setShowingSheet(false) ModalBottomSheetValue.Hidden -> setShowingSheet(false)
ModalBottomSheetValue.Expanded, ModalBottomSheetValue.Expanded,

View File

@@ -49,7 +49,7 @@ fun LibrarySheet(
librarySort: @Composable () -> Unit, librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit, libraryDisplay: @Composable () -> Unit,
) { ) {
val pagerState = rememberPagerState() val pagerState = rememberPagerState { LibrarySheetTabs.values().size }
val selectedPage = pagerState.currentPage val selectedPage = pagerState.currentPage
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
@@ -72,7 +72,6 @@ fun LibrarySheet(
} }
} }
HorizontalPager( HorizontalPager(
pageCount = LibrarySheetTabs.values().size,
state = pagerState, state = pagerState,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
) { ) {

View File

@@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumedWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -110,7 +110,11 @@ fun WideMainMenu(
} }
withDisplayController(controller) { withDisplayController(controller) {
val insets = WindowInsets.navigationBars.only(WindowInsetsSides.Start) val insets = WindowInsets.navigationBars.only(WindowInsetsSides.Start)
MainWindow(navigator, Modifier.padding(start = startPadding).windowInsetsPadding(insets).consumedWindowInsets(insets)) MainWindow(navigator,
Modifier.padding(start = startPadding)
.windowInsetsPadding(insets)
.consumeWindowInsets(insets)
)
} }
} }
} }

View File

@@ -35,6 +35,7 @@ import ca.gosyer.jui.uicore.insets.statusBars
import ca.gosyer.jui.uicore.resources.stringResource import ca.gosyer.jui.uicore.resources.stringResource
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.ui.compose.Libraries import com.mikepenz.aboutlibraries.ui.compose.Libraries
import com.mikepenz.aboutlibraries.ui.compose.util.StableLibrary
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
@@ -58,10 +59,10 @@ fun LicensesContent() {
val state = rememberLazyListState() val state = rememberLazyListState()
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
Libraries( Libraries(
libraries = remember(libs) { libs.libraries.toImmutableList() }, libraries = remember(libs) { libs.libraries.map { StableLibrary(it) }.toImmutableList() },
lazyListState = state, lazyListState = state,
onLibraryClick = { onLibraryClick = {
it.website?.let(uriHandler::openUri) it.library.website?.let(uriHandler::openUri)
}, },
contentPadding = WindowInsets.bottomNav.add( contentPadding = WindowInsets.bottomNav.add(
WindowInsets.navigationBars.only( WindowInsets.navigationBars.only(

View File

@@ -33,7 +33,6 @@ import ca.gosyer.jui.ui.base.chapter.ChapterDownloadState
import ca.gosyer.jui.ui.base.model.StableHolder import ca.gosyer.jui.ui.base.model.StableHolder
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import ca.gosyer.jui.uicore.vm.ViewModel import ca.gosyer.jui.uicore.vm.ViewModel
import cafe.adriel.voyager.core.model.coroutineScope
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -94,7 +93,7 @@ class MangaScreenViewModel
private val loadingManga = MutableStateFlow(true) private val loadingManga = MutableStateFlow(true)
private val loadingChapters = MutableStateFlow(true) private val loadingChapters = MutableStateFlow(true)
val isLoading = combine(loadingManga, loadingChapters) { a, b -> a || b } val isLoading = combine(loadingManga, loadingChapters) { a, b -> a || b }
.stateIn(coroutineScope, SharingStarted.Eagerly, true) .stateIn(scope, SharingStarted.Eagerly, true)
val categories = getCategories.asFlow(true) val categories = getCategories.asFlow(true)
.map { it.toImmutableList() } .map { it.toImmutableList() }

View File

@@ -6,8 +6,8 @@
package ca.gosyer.jui.ui.reader package ca.gosyer.jui.ui.reader
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPage
import ca.gosyer.jui.domain.reader.service.ReaderPreferences import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory
import ca.gosyer.jui.ui.reader.loader.PagesState import ca.gosyer.jui.ui.reader.loader.PagesState
import ca.gosyer.jui.ui.reader.loader.TachideskPageLoader import ca.gosyer.jui.ui.reader.loader.TachideskPageLoader
@@ -23,7 +23,7 @@ import org.lighthousegames.logging.logging
class ChapterLoader( class ChapterLoader(
private val readerPreferences: ReaderPreferences, private val readerPreferences: ReaderPreferences,
private val getChapterPage: GetChapterPage, private val http: Http,
private val chapterCache: DiskCache, private val chapterCache: DiskCache,
private val bitmapDecoderFactory: BitmapDecoderFactory, private val bitmapDecoderFactory: BitmapDecoderFactory,
) { ) {
@@ -34,7 +34,7 @@ class ChapterLoader(
chapter.state = ReaderChapter.State.Loading chapter.state = ReaderChapter.State.Loading
log.debug { "Loading pages for ${chapter.chapter.name}" } log.debug { "Loading pages for ${chapter.chapter.name}" }
val loader = TachideskPageLoader(chapter, readerPreferences, getChapterPage, chapterCache, bitmapDecoderFactory) val loader = TachideskPageLoader(chapter, readerPreferences, http, chapterCache, bitmapDecoderFactory)
val pages = loader.getPages() val pages = loader.getPages()

View File

@@ -22,6 +22,7 @@ import ca.gosyer.jui.domain.manga.model.MangaMeta
import ca.gosyer.jui.domain.reader.ReaderModeWatch import ca.gosyer.jui.domain.reader.ReaderModeWatch
import ca.gosyer.jui.domain.reader.model.Direction import ca.gosyer.jui.domain.reader.model.Direction
import ca.gosyer.jui.domain.reader.service.ReaderPreferences import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.ui.base.ChapterCache import ca.gosyer.jui.ui.base.ChapterCache
import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory
import ca.gosyer.jui.ui.base.model.StableHolder import ca.gosyer.jui.ui.base.model.StableHolder
@@ -82,6 +83,7 @@ class ReaderMenuViewModel
private val updateMangaMeta: UpdateMangaMeta, private val updateMangaMeta: UpdateMangaMeta,
private val updateChapterMeta: UpdateChapterMeta, private val updateChapterMeta: UpdateChapterMeta,
private val chapterCache: ChapterCache, private val chapterCache: ChapterCache,
private val http: Http,
contextWrapper: ContextWrapper, contextWrapper: ContextWrapper,
@Assisted private val params: Params, @Assisted private val params: Params,
) : ViewModel(contextWrapper) { ) : ViewModel(contextWrapper) {
@@ -152,7 +154,7 @@ class ReaderMenuViewModel
private val loader = ChapterLoader( private val loader = ChapterLoader(
readerPreferences = readerPreferences, readerPreferences = readerPreferences,
getChapterPage = getChapterPage, http = http,
chapterCache = chapterCache, chapterCache = chapterCache,
bitmapDecoderFactory = BitmapDecoderFactory(contextWrapper), bitmapDecoderFactory = BitmapDecoderFactory(contextWrapper),
) )

View File

@@ -13,6 +13,7 @@ import ca.gosyer.jui.core.lang.PriorityChannel
import ca.gosyer.jui.core.lang.throwIfCancellation import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPage import ca.gosyer.jui.domain.chapter.interactor.GetChapterPage
import ca.gosyer.jui.domain.reader.service.ReaderPreferences import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory import ca.gosyer.jui.ui.base.image.BitmapDecoderFactory
import ca.gosyer.jui.ui.base.model.StableHolder import ca.gosyer.jui.ui.base.model.StableHolder
import ca.gosyer.jui.ui.reader.model.ReaderChapter import ca.gosyer.jui.ui.reader.model.ReaderChapter
@@ -23,10 +24,10 @@ import com.seiko.imageloader.asImageBitmap
import com.seiko.imageloader.cache.disk.DiskCache import com.seiko.imageloader.cache.disk.DiskCache
import com.seiko.imageloader.component.decoder.DecodeResult import com.seiko.imageloader.component.decoder.DecodeResult
import com.seiko.imageloader.model.DataSource import com.seiko.imageloader.model.DataSource
import com.seiko.imageloader.model.ImageRequest
import com.seiko.imageloader.model.ImageResult import com.seiko.imageloader.model.ImageResult
import com.seiko.imageloader.option.Options import com.seiko.imageloader.option.Options
import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.onDownload
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel import io.ktor.client.statement.bodyAsChannel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
@@ -53,7 +55,7 @@ import org.lighthousegames.logging.logging
class TachideskPageLoader( class TachideskPageLoader(
val chapter: ReaderChapter, val chapter: ReaderChapter,
readerPreferences: ReaderPreferences, readerPreferences: ReaderPreferences,
private val getChapterPage: GetChapterPage, private val http: Http,
private val chapterCache: DiskCache, private val chapterCache: DiskCache,
private val bitmapDecoderFactory: BitmapDecoderFactory, private val bitmapDecoderFactory: BitmapDecoderFactory,
) : PageLoader() { ) : PageLoader() {
@@ -109,11 +111,15 @@ class TachideskPageLoader(
private suspend fun fetchImage(page: ReaderPage) { private suspend fun fetchImage(page: ReaderPage) {
log.debug { "Loading page ${page.index}" } log.debug { "Loading page ${page.index}" }
getChapterPage.asFlow(chapter.chapter, page.index) { flow {
val response = http.get("api/v1/manga/${chapter.chapter.mangaId}/chapter/${chapter.chapter.index}/page/${page.index}") {
onDownload { bytesSentTotal, contentLength -> onDownload { bytesSentTotal, contentLength ->
page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F) page.progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
} }
} }
emit(response)
}
.onEach { .onEach {
putImageInCache(it, page) putImageInCache(it, page)
page.bitmap.value = StableHolder { getImageFromCache(page) } page.bitmap.value = StableHolder { getImageFromCache(page) }
@@ -134,7 +140,7 @@ class TachideskPageLoader(
response: HttpResponse, response: HttpResponse,
page: ReaderPage, page: ReaderPage,
) { ) {
val editor = chapterCache.edit(page.cacheKey) val editor = chapterCache.openEditor(page.cacheKey)
?: throw Exception("Couldn't open cache") ?: throw Exception("Couldn't open cache")
try { try {
FileSystem.SYSTEM.write(editor.data) { FileSystem.SYSTEM.write(editor.data) {
@@ -150,18 +156,17 @@ class TachideskPageLoader(
} }
private suspend fun getImageFromCache(page: ReaderPage): ReaderPage.ImageDecodeState { private suspend fun getImageFromCache(page: ReaderPage): ReaderPage.ImageDecodeState {
return chapterCache[page.cacheKey]?.use { return chapterCache.openSnapshot(page.cacheKey)?.use {
it.source().use { source -> it.source().use { source ->
val decoder = bitmapDecoderFactory.create( val decoder = bitmapDecoderFactory.create(
ImageResult.Source( ImageResult.OfSource(
ImageRequest(Any()),
source, source,
DataSource.Engine, DataSource.Engine,
), ),
Options(), Options(),
) )
if (decoder != null) { if (decoder != null) {
runCatching { decoder.decode() as DecodeResult.Bitmap } runCatching { decoder.decode() as DecodeResult.OfBitmap }
.mapCatching { .mapCatching {
ReaderPage.ImageDecodeState.Success( ReaderPage.ImageDecodeState.Success(
it.bitmap.asImageBitmap().also { it.bitmap.asImageBitmap().also {

View File

@@ -86,6 +86,7 @@ class SettingsServerScreen : Screen {
expect class SettingsServerHostViewModel : ViewModel expect class SettingsServerHostViewModel : ViewModel
@Composable
expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit expect fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit
class SettingsServerViewModel class SettingsServerViewModel

View File

@@ -12,7 +12,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -228,11 +227,7 @@ private fun SourceWideScreenContent(
if (showingFilters && !isLatest) { if (showingFilters && !isLatest) {
Box( Box(
Modifier.fillMaxSize().pointerInput(loading) { Modifier.fillMaxSize().pointerInput(loading) {
forEachGesture { detectTapGestures(onTap = { setShowingFilters(false) })
detectTapGestures {
setShowingFilters(false)
}
}
}, },
) )
} }
@@ -287,7 +282,7 @@ private fun SourceThinScreenContent(
) { ) {
val bottomSheetState = rememberModalBottomSheetState( val bottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden, ModalBottomSheetValue.Hidden,
confirmStateChange = { confirmValueChange = {
when (it) { when (it) {
ModalBottomSheetValue.Hidden -> setShowingFilters(false) ModalBottomSheetValue.Hidden -> setShowingFilters(false)
ModalBottomSheetValue.Expanded, ModalBottomSheetValue.Expanded,
@@ -364,11 +359,7 @@ private fun SourceThinScreenContent(
if (showingFilters && !isLatest) { if (showingFilters && !isLatest) {
Box( Box(
Modifier.fillMaxSize().pointerInput(loading) { Modifier.fillMaxSize().pointerInput(loading) {
forEachGesture { detectTapGestures(onTap = { setShowingFilters(false) })
detectTapGestures {
setShowingFilters(false)
}
}
}, },
) )
} }

View File

@@ -8,13 +8,35 @@ package ca.gosyer.jui.ui.base.components
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
@OptIn(ExperimentalFoundationApi::class)
actual typealias TooltipPlacement = androidx.compose.foundation.TooltipPlacement actual typealias TooltipPlacement = androidx.compose.foundation.TooltipPlacement
actual typealias CursorPointImpl = androidx.compose.foundation.TooltipPlacement.CursorPoint @OptIn(ExperimentalFoundationApi::class)
actual class CursorPointImpl actual constructor(
offset: DpOffset,
alignment: Alignment,
windowMargin: Dp,
) : TooltipPlacement by androidx.compose.foundation.TooltipPlacement.CursorPoint(
offset = offset,
alignment = alignment,
windowMargin = windowMargin
)
actual typealias ComponentRectImpl = androidx.compose.foundation.TooltipPlacement.ComponentRect @OptIn(ExperimentalFoundationApi::class)
actual class ComponentRectImpl actual constructor(
anchor: Alignment,
alignment: Alignment,
offset: DpOffset,
) : TooltipPlacement by androidx.compose.foundation.TooltipPlacement.ComponentRect(
anchor = anchor,
alignment = alignment,
offset = offset
)
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable

View File

@@ -9,8 +9,10 @@ package ca.gosyer.jui.ui.base.image
import ca.gosyer.jui.core.io.userDataDir import ca.gosyer.jui.core.io.userDataDir
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.Bitmap
import com.seiko.imageloader.cache.disk.DiskCacheBuilder import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryKey
import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.component.ComponentRegistryBuilder
import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.component.setupDefaultComponents
import com.seiko.imageloader.option.OptionsBuilder import com.seiko.imageloader.option.OptionsBuilder
@@ -33,5 +35,5 @@ actual fun DiskCacheBuilder.configure(
maxSizeBytes(1024 * 1024 * 150) // 150 MB maxSizeBytes(1024 * 1024 * 150) // 150 MB
} }
actual fun MemoryCacheBuilder.configure(contextWrapper: ContextWrapper) { actual fun MemoryCacheBuilder<MemoryKey, Bitmap>.configure(contextWrapper: ContextWrapper) {
} }

View File

@@ -8,8 +8,10 @@ package ca.gosyer.jui.ui.base.image
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.Bitmap
import com.seiko.imageloader.cache.disk.DiskCacheBuilder import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryKey
import com.seiko.imageloader.cache.memory.maxSizePercent import com.seiko.imageloader.cache.memory.maxSizePercent
import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.component.ComponentRegistryBuilder
import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.component.setupDefaultComponents
@@ -47,6 +49,6 @@ private fun getCacheDir(): String {
)!!.path.orEmpty() )!!.path.orEmpty()
} }
actual fun MemoryCacheBuilder.configure(contextWrapper: ContextWrapper) { actual fun MemoryCacheBuilder<MemoryKey, Bitmap>.configure(contextWrapper: ContextWrapper) {
maxSizePercent(0.25) maxSizePercent(0.25)
} }

View File

@@ -10,7 +10,7 @@ plugins {
} }
kotlin { kotlin {
android { androidTarget {
compilations { compilations {
all { all {
kotlinOptions.jvmTarget = Config.androidJvmTarget.toString() kotlinOptions.jvmTarget = Config.androidJvmTarget.toString()
@@ -28,6 +28,21 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
applyHierarchyTemplate {
common {
group("jvm") {
withAndroidTarget()
withJvm()
}
group("ios") {
withIosX64()
withIosArm64()
withIosSimulatorArm64()
}
}
}
sourceSets { sourceSets {
all { all {
languageSettings { languageSettings {
@@ -63,29 +78,24 @@ kotlin {
} }
} }
val jvmMain by creating { val jvmMain by getting {
dependsOn(commonMain)
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
api(compose.desktop.currentOs) api(compose.desktop.currentOs)
} }
} }
val jvmTest by creating { val jvmTest by getting {
dependsOn(commonTest)
dependencies { dependencies {
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
val desktopMain by getting { val desktopMain by getting {
dependsOn(jvmMain)
} }
val desktopTest by getting { val desktopTest by getting {
dependsOn(jvmTest)
} }
val androidMain by getting { val androidMain by getting {
dependsOn(jvmMain)
dependencies { dependencies {
api(libs.bundles.compose.android) api(libs.bundles.compose.android)
api(libs.androidx.core) api(libs.androidx.core)
@@ -93,23 +103,6 @@ kotlin {
} }
} }
val androidUnitTest by getting { val androidUnitTest by getting {
dependsOn(jvmTest)
}
val iosMain by creating {
dependsOn(commonMain)
}
val iosTest by creating {
dependsOn(commonTest)
}
listOf(
"iosX64",
"iosArm64",
"iosSimulatorArm64",
).forEach {
getByName(it + "Main").dependsOn(iosMain)
getByName(it + "Test").dependsOn(iosTest)
} }
} }
} }

View File

@@ -35,9 +35,11 @@ import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQ
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ca.gosyer.jui.uicore.components.LoadingScreen import ca.gosyer.jui.uicore.components.LoadingScreen
import com.seiko.imageloader.ImageRequestState import com.seiko.imageloader.model.ImageAction
import com.seiko.imageloader.model.ImageRequest import com.seiko.imageloader.model.ImageRequest
import com.seiko.imageloader.rememberAsyncImagePainter import com.seiko.imageloader.rememberImageAction
import com.seiko.imageloader.rememberImageActionPainter
import com.seiko.imageloader.rememberImagePainter
import org.lighthousegames.logging.logging import org.lighthousegames.logging.logging
private val log = logging() private val log = logging()
@@ -84,32 +86,31 @@ fun ImageLoaderImage(
) { ) {
key(data) { key(data) {
val request = remember { ImageRequest(data) } val request = remember { ImageRequest(data) }
val painter = rememberAsyncImagePainter( if (animationSpec != null) {
request, val imageAction by rememberImageAction(request)
contentScale = contentScale,
filterQuality = filterQuality,
)
val progress = remember { mutableStateOf(-1F) } val progress = remember { mutableStateOf(-1F) }
val error = remember { mutableStateOf<Throwable?>(null) } val error = remember { mutableStateOf<Throwable?>(null) }
val state by derivedStateOf { val state by derivedStateOf {
when (val state = painter.requestState) { when (val action = imageAction) {
is ImageRequestState.Failure -> { is ImageAction.Failure -> {
progress.value = 0.0F progress.value = 0.0F
error.value = state.error error.value = action.error
ImageLoaderImageState.Failure ImageLoaderImageState.Failure
} }
is ImageRequestState.Loading -> { is ImageAction.Loading -> {
progress.value = 0.0F progress.value = 0.0F
ImageLoaderImageState.Loading ImageLoaderImageState.Loading
} }
ImageRequestState.Success -> { is ImageAction.Success -> {
progress.value = 1.0F progress.value = 1.0F
ImageLoaderImageState.Success ImageLoaderImageState.Success
} }
else -> {
ImageLoaderImageState.Loading
}
} }
} }
if (animationSpec != null) {
Crossfade(state, animationSpec = animationSpec, modifier = modifier) { Crossfade(state, animationSpec = animationSpec, modifier = modifier) {
Box(Modifier.fillMaxSize(), contentAlignment) { Box(Modifier.fillMaxSize(), contentAlignment) {
when (it) { when (it) {
@@ -117,7 +118,10 @@ fun ImageLoaderImage(
onLoading(progress.value) onLoading(progress.value)
} }
ImageLoaderImageState.Success -> Image( ImageLoaderImageState.Success -> Image(
painter = painter, painter = rememberImageActionPainter(
imageAction,
filterQuality = filterQuality
),
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
alignment = alignment, alignment = alignment,
@@ -136,7 +140,7 @@ fun ImageLoaderImage(
} else { } else {
Box(modifier, contentAlignment) { Box(modifier, contentAlignment) {
Image( Image(
painter = painter, painter = rememberImagePainter(request, filterQuality = filterQuality),
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
alignment = alignment, alignment = alignment,

View File

@@ -6,9 +6,12 @@
package ca.gosyer.jui.uicore.pager package ca.gosyer.jui.uicore.pager
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
import androidx.compose.foundation.gestures.snapping.SnapPositionInLayout
import androidx.compose.foundation.gestures.snapping.SnapPositionInLayout.Companion.CenterToCenter
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -38,6 +41,8 @@ import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.util.fastMaxBy
import androidx.compose.ui.util.fastSumBy import androidx.compose.ui.util.fastSumBy
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlin.math.abs
import kotlin.math.sign
@Composable @Composable
fun VerticalPager( fun VerticalPager(
@@ -229,9 +234,7 @@ class PagerState(
// https://android.googlesource.com/platform/frameworks/support/+/refs/changes/78/2160778/35/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt // https://android.googlesource.com/platform/frameworks/support/+/refs/changes/78/2160778/35/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
private fun lazyListSnapLayoutInfoProvider( private fun lazyListSnapLayoutInfoProvider(
lazyListState: LazyListState, lazyListState: LazyListState,
positionInLayout: (layoutSize: Float, itemSize: Float) -> Float = { layoutSize, itemSize -> positionInLayout: SnapPositionInLayout = CenterToCenter
layoutSize / 2f - itemSize / 2f
},
) = object : SnapLayoutInfoProvider { ) = object : SnapLayoutInfoProvider {
private val layoutInfo: LazyListLayoutInfo private val layoutInfo: LazyListLayoutInfo
get() = lazyListState.layoutInfo get() = lazyListState.layoutInfo
@@ -239,13 +242,21 @@ private fun lazyListSnapLayoutInfoProvider(
// Single page snapping is the default // Single page snapping is the default
override fun Density.calculateApproachOffset(initialVelocity: Float): Float = 0f override fun Density.calculateApproachOffset(initialVelocity: Float): Float = 0f
override fun Density.calculateSnappingOffsetBounds(): ClosedFloatingPointRange<Float> { override fun Density.calculateSnappingOffset(currentVelocity: Float): Float {
var lowerBoundOffset = Float.NEGATIVE_INFINITY var lowerBoundOffset = Float.NEGATIVE_INFINITY
var upperBoundOffset = Float.POSITIVE_INFINITY var upperBoundOffset = Float.POSITIVE_INFINITY
layoutInfo.visibleItemsInfo.fastForEach { item -> layoutInfo.visibleItemsInfo.fastForEach { item ->
val offset = val offset =
calculateDistanceToDesiredSnapPosition(layoutInfo, item, positionInLayout) calculateDistanceToDesiredSnapPosition(
mainAxisViewPortSize = layoutInfo.singleAxisViewportSize,
beforeContentPadding = layoutInfo.beforeContentPadding,
afterContentPadding = layoutInfo.afterContentPadding,
itemSize = item.size,
itemOffset = item.offset,
itemIndex = item.index,
snapPositionInLayout = positionInLayout
)
// Find item that is closest to the center // Find item that is closest to the center
if (offset <= 0 && offset > lowerBoundOffset) { if (offset <= 0 && offset > lowerBoundOffset) {
@@ -258,7 +269,58 @@ private fun lazyListSnapLayoutInfoProvider(
} }
} }
return lowerBoundOffset.rangeTo(upperBoundOffset) return calculateFinalOffset(
currentVelocity,
lowerBoundOffset,
upperBoundOffset
)
}
@OptIn(ExperimentalFoundationApi::class)
private fun Density.calculateDistanceToDesiredSnapPosition(
mainAxisViewPortSize: Int,
beforeContentPadding: Int,
afterContentPadding: Int,
itemSize: Int,
itemOffset: Int,
itemIndex: Int,
snapPositionInLayout: SnapPositionInLayout
): Float {
val containerSize = mainAxisViewPortSize - beforeContentPadding - afterContentPadding
val desiredDistance = with(snapPositionInLayout) {
position(containerSize, itemSize, itemIndex)
}.toFloat()
return itemOffset - desiredDistance
}
private fun calculateFinalOffset(velocity: Float, lowerBound: Float, upperBound: Float): Float {
fun Float.isValidDistance(): Boolean {
return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY
}
val finalDistance = when (sign(velocity)) {
0f -> {
if (abs(upperBound) <= abs(lowerBound)) {
upperBound
} else {
lowerBound
}
}
1f -> upperBound
-1f -> lowerBound
else -> 0f
}
return if (finalDistance.isValidDistance()) {
finalDistance
} else {
0f
}
} }
override fun Density.calculateSnapStepSize(): Float = override fun Density.calculateSnapStepSize(): Float =
@@ -277,20 +339,5 @@ private fun rememberLazyListSnapFlingBehavior(lazyListState: LazyListState): Fli
return rememberSnapFlingBehavior(snappingLayout) return rememberSnapFlingBehavior(snappingLayout)
} }
private fun calculateDistanceToDesiredSnapPosition(
layoutInfo: LazyListLayoutInfo,
item: LazyListItemInfo,
positionInLayout: (layoutSize: Float, itemSize: Float) -> Float,
): Float {
val containerSize =
with(layoutInfo) { singleAxisViewportSize - beforeContentPadding - afterContentPadding }
val desiredDistance =
positionInLayout(containerSize.toFloat(), item.size.toFloat())
val itemCurrentPosition = item.offset
return itemCurrentPosition - desiredDistance
}
private val LazyListLayoutInfo.singleAxisViewportSize: Int private val LazyListLayoutInfo.singleAxisViewportSize: Int
get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width

View File

@@ -10,7 +10,7 @@ import ca.gosyer.jui.core.lang.launchUI
import ca.gosyer.jui.core.prefs.Preference import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -20,7 +20,7 @@ import kotlinx.coroutines.launch
abstract class ViewModel(private val contextWrapper: ContextWrapper) : ScreenModel { abstract class ViewModel(private val contextWrapper: ContextWrapper) : ScreenModel {
protected open val scope: CoroutineScope protected open val scope: CoroutineScope
get() = coroutineScope get() = screenModelScope
fun <T> Preference<T>.asStateFlow() = PreferenceMutableStateFlow(this, scope) fun <T> Preference<T>.asStateFlow() = PreferenceMutableStateFlow(this, scope)

View File

@@ -6,6 +6,7 @@
package ca.gosyer.jui.uicore.components package ca.gosyer.jui.uicore.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.hoverable import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.HoverInteraction import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -14,12 +15,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@OptIn(ExperimentalFoundationApi::class, ExperimentalCoroutinesApi::class)
actual fun Modifier.buttonModifier( actual fun Modifier.buttonModifier(
onClick: () -> Unit, onClick: () -> Unit,
onHintClick: () -> Unit, onHintClick: () -> Unit,