diff --git a/komelia-app/androidApp/src/main/kotlin/snd/komelia/App.kt b/komelia-app/androidApp/src/main/kotlin/snd/komelia/App.kt index 2b1530f52..9864c6073 100644 --- a/komelia-app/androidApp/src/main/kotlin/snd/komelia/App.kt +++ b/komelia-app/androidApp/src/main/kotlin/snd/komelia/App.kt @@ -38,7 +38,7 @@ class App : Application() { private fun initWorkManager() { val config = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.DEBUG) - .setWorkerFactory(MyWorkerFactory(dependencies.filterNotNull().map { it.offlineDependencies })) + .setWorkerFactory(MyWorkerFactory(dependencies.filterNotNull().map { it.offlineDependencies }.filterNotNull())) .setWorkerCoroutineContext(Dispatchers.IO) .build() WorkManager.initialize(this, config) diff --git a/komelia-domain/core/src/commonMain/kotlin/snd/komelia/settings/ImageReaderSettingsRepository.kt b/komelia-domain/core/src/commonMain/kotlin/snd/komelia/settings/ImageReaderSettingsRepository.kt index 4ce074604..b928e28b7 100644 --- a/komelia-domain/core/src/commonMain/kotlin/snd/komelia/settings/ImageReaderSettingsRepository.kt +++ b/komelia-domain/core/src/commonMain/kotlin/snd/komelia/settings/ImageReaderSettingsRepository.kt @@ -25,6 +25,9 @@ interface ImageReaderSettingsRepository { fun getPagedReaderScaleType(): Flow suspend fun putPagedReaderScaleType(type: LayoutScaleType) + fun getGtcModeEnabled(): Flow + suspend fun putGtcModeEnabled(enabled: Boolean) + fun getPagedReaderReadingDirection(): Flow suspend fun putPagedReaderReadingDirection(direction: PagedReadingDirection) diff --git a/komelia-epub-reader/ttu-ebook-reader/package-lock.json b/komelia-epub-reader/ttu-ebook-reader/package-lock.json index 716fae43c..aeb64ce95 100644 --- a/komelia-epub-reader/ttu-ebook-reader/package-lock.json +++ b/komelia-epub-reader/ttu-ebook-reader/package-lock.json @@ -2944,6 +2944,20 @@ } } }, + "node_modules/svelte-check/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/svelte-fa": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/svelte-fa/-/svelte-fa-4.0.3.tgz", diff --git a/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/ImageReaderSettings.kt b/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/ImageReaderSettings.kt index 8b279773f..6ce7fd8f1 100644 --- a/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/ImageReaderSettings.kt +++ b/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/ImageReaderSettings.kt @@ -18,6 +18,7 @@ data class ImageReaderSettings( val readerType: ReaderType = PAGED, val stretchToFit: Boolean = true, val pagedScaleType: LayoutScaleType = LayoutScaleType.SCREEN, + val gtcModeEnabled: Boolean = false, val pagedReadingDirection: PagedReadingDirection = PagedReadingDirection.LEFT_TO_RIGHT, val pagedPageLayout: PageDisplayLayout = PageDisplayLayout.SINGLE_PAGE, val continuousReadingDirection: ContinuousReadingDirection = ContinuousReadingDirection.TOP_TO_BOTTOM, diff --git a/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/repository/ReaderSettingsRepositoryWrapper.kt b/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/repository/ReaderSettingsRepositoryWrapper.kt index 59e4c5534..443fac8e9 100644 --- a/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/repository/ReaderSettingsRepositoryWrapper.kt +++ b/komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/repository/ReaderSettingsRepositoryWrapper.kt @@ -51,6 +51,14 @@ class ReaderSettingsRepositoryWrapper( wrapper.transform { it.copy(pagedScaleType = type) } } + override fun getGtcModeEnabled(): Flow { + return wrapper.mapState { it.gtcModeEnabled } + } + + override suspend fun putGtcModeEnabled(enabled: Boolean) { + wrapper.transform { it.copy(gtcModeEnabled = enabled) } + } + override fun getPagedReaderReadingDirection(): Flow { return wrapper.mapState { it.pagedReadingDirection } } diff --git a/komelia-infra/database/sqlite/build.gradle.kts b/komelia-infra/database/sqlite/build.gradle.kts index 61c6cbdc2..f5577616e 100644 --- a/komelia-infra/database/sqlite/build.gradle.kts +++ b/komelia-infra/database/sqlite/build.gradle.kts @@ -47,8 +47,8 @@ kotlin { implementation(libs.exposed.kotlin.datetime) implementation(libs.hikariCP) implementation(libs.flyway.core) -// implementation(libs.sqlite.xerial.jdbc) - implementation(files("sqlite-jdbc-3.51.3.0-linux.jar")) + implementation(libs.sqlite.xerial.jdbc) +//// implementation(files("sqlite-jdbc-3.51.3.0-linux.jar")) } } } diff --git a/komelia-infra/database/sqlite/src/androidMain/jniLibs/arm64-v8a/libsqlitejdbc.so b/komelia-infra/database/sqlite/src/androidMain/jniLibs/arm64-v8a/libsqlitejdbc.so new file mode 100644 index 000000000..c3eedf920 Binary files /dev/null and b/komelia-infra/database/sqlite/src/androidMain/jniLibs/arm64-v8a/libsqlitejdbc.so differ diff --git a/komelia-infra/database/sqlite/src/commonMain/composeResources/files/migrations/app/V13__gtc_page_orientation.sql b/komelia-infra/database/sqlite/src/commonMain/composeResources/files/migrations/app/V13__gtc_page_orientation.sql new file mode 100755 index 000000000..6559e5600 --- /dev/null +++ b/komelia-infra/database/sqlite/src/commonMain/composeResources/files/migrations/app/V13__gtc_page_orientation.sql @@ -0,0 +1,2 @@ +ALTER TABLE ImageReaderSettings + ADD COLUMN gtc_mode_enabled BOOLEAN DEFAULT 0 NOT NULL; diff --git a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/migrations/AppMigrations.kt b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/migrations/AppMigrations.kt index 625ba6a9e..48d56832b 100644 --- a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/migrations/AppMigrations.kt +++ b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/migrations/AppMigrations.kt @@ -19,6 +19,7 @@ class AppMigrations : MigrationResourcesProvider() { "V10__komf_settings.sql", "V11__home_filters.sql", "V12__offline_mode.sql", + "V13__gtc_page_orientation.sql", ) override suspend fun getMigration(name: String): ByteArray? { diff --git a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/ExposedImageReaderSettingsRepository.kt b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/ExposedImageReaderSettingsRepository.kt index 1049c787e..f910e2eeb 100644 --- a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/ExposedImageReaderSettingsRepository.kt +++ b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/ExposedImageReaderSettingsRepository.kt @@ -33,6 +33,7 @@ class ExposedImageReaderSettingsRepository(database: Database) : ExposedReposito readerType = ReaderType.valueOf(it[ImageReaderSettingsTable.readerType]), stretchToFit = it[ImageReaderSettingsTable.stretchToFit], pagedScaleType = LayoutScaleType.valueOf(it[ImageReaderSettingsTable.pagedScaleType]), + gtcModeEnabled = it[ImageReaderSettingsTable.gtcModeEnabled], pagedReadingDirection = PagedReadingDirection.valueOf(it[ImageReaderSettingsTable.pagedReadingDirection]), pagedPageLayout = PageDisplayLayout.valueOf(it[ImageReaderSettingsTable.pagedPageLayout]), continuousReadingDirection = ContinuousReadingDirection.valueOf(it[ImageReaderSettingsTable.continuousReadingDirection]), @@ -65,6 +66,7 @@ class ExposedImageReaderSettingsRepository(database: Database) : ExposedReposito it[readerType] = settings.readerType.name it[stretchToFit] = settings.stretchToFit it[pagedScaleType] = settings.pagedScaleType.name + it[gtcModeEnabled] = settings.gtcModeEnabled it[pagedReadingDirection] = settings.pagedReadingDirection.name it[pagedPageLayout] = settings.pagedPageLayout.name it[continuousReadingDirection] = settings.continuousReadingDirection.name diff --git a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/ImageReaderSettingsTable.kt b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/ImageReaderSettingsTable.kt index 45bf986d6..256c342e9 100644 --- a/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/ImageReaderSettingsTable.kt +++ b/komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/ImageReaderSettingsTable.kt @@ -9,6 +9,7 @@ object ImageReaderSettingsTable : Table("ImageReaderSettings") { val stretchToFit = bool("stretch_to_fit") val pagedScaleType = text("paged_scale_type") + val gtcModeEnabled = bool("gtc_mode_enabled") val pagedReadingDirection = text("paged_reading_direction") val pagedPageLayout = text("paged_page_layout") diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlicommon.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlicommon.so new file mode 100644 index 000000000..4460cf9ae Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlicommon.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlidec.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlidec.so new file mode 100644 index 000000000..b37b0357f Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlidec.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlienc.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlienc.so new file mode 100644 index 000000000..e91ea1187 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libbrotlienc.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libdav1d.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libdav1d.so new file mode 100755 index 000000000..7bfbe89d2 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libdav1d.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libde265.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libde265.so new file mode 100644 index 000000000..65e70f77a Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libde265.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexif.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexif.so new file mode 100755 index 000000000..47da4434d Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexif.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexpat.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexpat.so new file mode 100644 index 000000000..cad6ff8b0 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libexpat.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libffi.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libffi.so new file mode 100755 index 000000000..a74e3967b Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libffi.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgio-2.0.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgio-2.0.so new file mode 100755 index 000000000..9916c101d Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgio-2.0.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libglib-2.0.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libglib-2.0.so new file mode 100755 index 000000000..43fa3e102 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libglib-2.0.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgmodule-2.0.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgmodule-2.0.so new file mode 100755 index 000000000..46194c4d0 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgmodule-2.0.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgobject-2.0.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgobject-2.0.so new file mode 100755 index 000000000..bf5b22001 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libgobject-2.0.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libheif.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libheif.so new file mode 100644 index 000000000..dd23a7a8a Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libheif.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libhwy.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libhwy.so new file mode 100644 index 000000000..f067c065a Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libhwy.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libiconv.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libiconv.so new file mode 100755 index 000000000..efa397335 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libiconv.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libintl.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libintl.so new file mode 100755 index 000000000..5455fa87c Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libintl.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjpeg.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjpeg.so new file mode 100644 index 000000000..2c6e70ba2 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjpeg.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl.so new file mode 100644 index 000000000..59cace01f Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_cms.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_cms.so new file mode 100644 index 000000000..c100542aa Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_cms.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_threads.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_threads.so new file mode 100644 index 000000000..8ba4e66cd Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libjxl_threads.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_android_bitmap.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_android_bitmap.so new file mode 100644 index 000000000..b8c87c39f Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_android_bitmap.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_onnxruntime.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_onnxruntime.so new file mode 100644 index 000000000..b2ceaa188 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_onnxruntime.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_vips.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_vips.so new file mode 100644 index 000000000..41f4d3728 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libkomelia_vips.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/liblcms2.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/liblcms2.so new file mode 100755 index 000000000..a2210b142 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/liblcms2.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libomp.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libomp.so new file mode 100755 index 000000000..f8af12702 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libomp.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libonnxruntime.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libonnxruntime.so new file mode 100644 index 000000000..63877ede9 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libonnxruntime.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libpng.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libpng.so new file mode 100644 index 000000000..008a48d8b Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libpng.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libsharpyuv.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libsharpyuv.so new file mode 100644 index 000000000..bf5a0eaf7 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libsharpyuv.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libtiff.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libtiff.so new file mode 100644 index 000000000..d694dc1da Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libtiff.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libturbojpeg.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libturbojpeg.so new file mode 100644 index 000000000..37cb647b0 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libturbojpeg.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libvips.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libvips.so new file mode 100755 index 000000000..8213c1c91 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libvips.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebp.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebp.so new file mode 100644 index 000000000..45d5c631a Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebp.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdecoder.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdecoder.so new file mode 100644 index 000000000..c4e2f0dae Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdecoder.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdemux.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdemux.so new file mode 100644 index 000000000..063cc69cd Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpdemux.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpmux.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpmux.so new file mode 100644 index 000000000..100b57cd4 Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libwebpmux.so differ diff --git a/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libz.so b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libz.so new file mode 100644 index 000000000..7c6fcac1e Binary files /dev/null and b/komelia-infra/jni/src/androidMain/jniLibs/arm64-v8a/libz.so differ diff --git a/komelia-ui/src/androidMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.android.kt b/komelia-ui/src/androidMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.android.kt new file mode 100755 index 000000000..56aa1e1d2 --- /dev/null +++ b/komelia-ui/src/androidMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.android.kt @@ -0,0 +1,31 @@ +package snd.komelia.ui.platform + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.content.pm.ActivityInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalContext + +@Composable +actual fun LockScreenOrientation(locked: Boolean) { + val activity = LocalContext.current.findActivity() ?: return + + DisposableEffect(locked) { + val previousOrientation = activity.requestedOrientation + activity.requestedOrientation = + if (locked) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + else ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + + onDispose { + activity.requestedOrientation = previousOrientation + } + } +} + +private tailrec fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} diff --git a/komelia-ui/src/commonMain/composeResources/files/komga.html b/komelia-ui/src/commonMain/composeResources/files/komga.html new file mode 100644 index 000000000..131554764 --- /dev/null +++ b/komelia-ui/src/commonMain/composeResources/files/komga.html @@ -0,0 +1,123 @@ + + + + + + + Komga Epub Reader + + + + + +
+ + + diff --git a/komelia-ui/src/commonMain/composeResources/files/ttsu.html b/komelia-ui/src/commonMain/composeResources/files/ttsu.html new file mode 100644 index 000000000..2763e7710 --- /dev/null +++ b/komelia-ui/src/commonMain/composeResources/files/ttsu.html @@ -0,0 +1,205 @@ + + + + + + + + epub reader + + + + +
+ + diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.kt new file mode 100755 index 000000000..ac118b901 --- /dev/null +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.kt @@ -0,0 +1,12 @@ +package snd.komelia.ui.platform + +import androidx.compose.runtime.Composable + +/** + * Locks the app to portrait orientation while [locked] is true, restoring whatever + * orientation setting was in effect before once [locked] goes back to false (or the + * composable leaves composition). No-op on platforms without an OS-level concept of + * screen orientation (desktop, web). + */ +@Composable +expect fun LockScreenOrientation(locked: Boolean) diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/GtcReaderSupport.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/GtcReaderSupport.kt new file mode 100644 index 000000000..fbf6531cc --- /dev/null +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/GtcReaderSupport.kt @@ -0,0 +1,98 @@ +package snd.komelia.ui.reader.image.paged + +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.toSize +import snd.komelia.image.ReaderImage +import snd.komelia.settings.model.PageDisplayLayout +import snd.komelia.settings.model.PageDisplayLayout.SINGLE_PAGE +import snd.komelia.ui.reader.image.PageMetadata +import snd.komelia.ui.reader.image.ScreenScaleState + +/** + * GTC (device-height orientation): when enabled, a landscape page (its longest side + * horizontal) viewed on a portrait device is rotated so its longest side aligns with + * the device height, and the image is scaled to fit before display. + * Only applies to single page layout - rotating a two-page spread isn't well-defined. + * + * All GTC-specific decision/scaling logic lives here so future upstream merges only + * need to reconcile the small one-line call sites left in PagedReaderState.kt and + * PagedReaderContent.kt, rather than colliding with inlined GTC logic. + */ +object GtcReaderSupport { + + fun isApplicable( + gtcModeEnabled: Boolean, + layout: PageDisplayLayout, + pageMetadata: PageMetadata, + containerSize: IntSize, + ): Boolean { + return gtcModeEnabled && + layout == SINGLE_PAGE && + pageMetadata.isLandscape() && + containerSize.height > containerSize.width + } + + fun adjustedContainerSize( + gtcModeEnabled: Boolean, + layout: PageDisplayLayout, + pages: List, + containerSize: IntSize, + ): IntSize { + val page = pages.singleOrNull() ?: return containerSize + return if (isApplicable(gtcModeEnabled, layout, page, containerSize)) + IntSize(containerSize.height, containerSize.width) + else containerSize + } + + fun swapIfApplicable(applicable: Boolean, size: IntSize): IntSize = + if (applicable) IntSize(size.height, size.width) else size + + suspend fun updateImage( + image: ReaderImage, + maxPageSize: IntSize, + stretchToFit: Boolean, + ) { + val imageDisplaySize = image.calculateSizeForArea(maxPageSize, stretchToFit) ?: maxPageSize + image.requestUpdate( + visibleDisplaySize = IntRect(0, 0, imageDisplaySize.width, imageDisplaySize.height), + zoomFactor = 1f, + maxDisplaySize = maxPageSize + ) + } + + /** + * fitToScreenSize is computed against a width/height-swapped maxPageSize (see + * adjustedContainerSize), so it is expressed in rotated-page space. Locks the view + * to a fit-to-screen scale, swapping the target size back to real screen space first + * so the zoom-limit math compares matching axes. + */ + fun applyFixedScale(scaleState: ScreenScaleState, fitToScreenSize: IntSize) { + scaleState.setTargetSize(swapIfApplicable(true, fitToScreenSize).toSize()) + scaleState.setZoom(0f) + } + + data class RotationLayout( + val rotate: Boolean, + val childConstraints: Constraints, + ) + + fun computeRotationLayout( + gtcModeEnabled: Boolean, + pageMetadata: PageMetadata, + constraints: Constraints, + ): RotationLayout { + val containerIsPortrait = constraints.maxHeight > constraints.maxWidth + val rotate = gtcModeEnabled && pageMetadata.isLandscape() && containerIsPortrait + val childConstraints = if (rotate) { + constraints.copy( + minWidth = constraints.minHeight, + maxWidth = constraints.maxHeight, + minHeight = constraints.minWidth, + maxHeight = constraints.maxWidth, + ) + } else constraints + return RotationLayout(rotate, childConstraints) + } +} diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderContent.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderContent.kt index 77a58a702..0c4dc57a0 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderContent.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderContent.kt @@ -33,6 +33,7 @@ import snd.komelia.settings.model.PagedReadingDirection.RIGHT_TO_LEFT import snd.komelia.ui.reader.image.ScreenScaleState import snd.komelia.ui.reader.image.common.PagedReaderHelpDialog import snd.komelia.ui.reader.image.common.ReaderControlsOverlay +import snd.komelia.ui.platform.LockScreenOrientation import snd.komelia.ui.reader.image.common.ReaderImageContent import snd.komelia.ui.reader.image.common.ScalableContainer import snd.komelia.ui.reader.image.paged.PagedReaderState.Page @@ -64,6 +65,8 @@ fun BoxScope.PagedReaderContent( val layoutOffset = pagedReaderState.layoutOffset.collectAsState().value val currentContainerSize = screenScaleState.areaSize.collectAsState().value + val gtcModeEnabled = pagedReaderState.gtcModeEnabled.collectAsState().value + LockScreenOrientation(locked = gtcModeEnabled) val coroutineScope = rememberCoroutineScope() ReaderControlsOverlay( @@ -96,7 +99,7 @@ fun BoxScope.PagedReaderContent( TransitionPage(transitionPage) } else { when (layout) { - SINGLE_PAGE -> pages.firstOrNull()?.let { SinglePageLayout(it) } + SINGLE_PAGE -> pages.firstOrNull()?.let { SinglePageLayout(it, gtcModeEnabled) } DOUBLE_PAGES, DOUBLE_PAGES_NO_COVER -> DoublePageLayout(pages, readingDirection) } } @@ -165,13 +168,34 @@ private fun TransitionPage(page: TransitionPage) { } @Composable -private fun SinglePageLayout(page: Page) { +private fun SinglePageLayout(page: Page, gtcModeEnabled: Boolean) { Layout(content = { ReaderImageContent(page.imageResult) }) { measurable, constraints -> - val placeable = measurable.first().measure(constraints) - val startPadding = (constraints.maxWidth - placeable.width) / 2 - val topPadding = ((constraints.maxHeight - placeable.height) / 2).coerceAtLeast(0) - layout(constraints.maxWidth, constraints.maxHeight) { - placeable.placeRelative(startPadding, topPadding) + val (rotate, childConstraints) = GtcReaderSupport.computeRotationLayout( + gtcModeEnabled, page.metadata, constraints + ) + + val placeable = measurable.first().measure(childConstraints) + + if (rotate) { + // after rotation the placeable's width/height swap on screen + val effectiveWidth = placeable.height + val effectiveHeight = placeable.width + val startPadding = (constraints.maxWidth - effectiveWidth) / 2 + val topPadding = ((constraints.maxHeight - effectiveHeight) / 2).coerceAtLeast(0) + layout(constraints.maxWidth, constraints.maxHeight) { + placeable.placeRelativeWithLayer( + x = startPadding + (effectiveWidth - placeable.width) / 2, + y = topPadding + (effectiveHeight - placeable.height) / 2, + ) { + rotationZ = 90f + } + } + } else { + val startPadding = (constraints.maxWidth - placeable.width) / 2 + val topPadding = ((constraints.maxHeight - placeable.height) / 2).coerceAtLeast(0) + layout(constraints.maxWidth, constraints.maxHeight) { + placeable.placeRelative(startPadding, topPadding) + } } } } diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderState.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderState.kt index 2a74acb12..2b2102a0c 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderState.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/paged/PagedReaderState.kt @@ -90,11 +90,13 @@ class PagedReaderState( val layout = MutableStateFlow(SINGLE_PAGE) val layoutOffset = MutableStateFlow(false) val scaleType = MutableStateFlow(LayoutScaleType.SCREEN) + val gtcModeEnabled = MutableStateFlow(false) val readingDirection = MutableStateFlow(LEFT_TO_RIGHT) suspend fun initialize() { layout.value = settingsRepository.getPagedReaderDisplayLayout().first() scaleType.value = settingsRepository.getPagedReaderScaleType().first() + gtcModeEnabled.value = settingsRepository.getGtcModeEnabled().first() readingDirection.value = when (readerState.series.value?.metadata?.readingDirection) { KomgaReadingDirection.LEFT_TO_RIGHT -> LEFT_TO_RIGHT KomgaReadingDirection.RIGHT_TO_LEFT -> RIGHT_TO_LEFT @@ -118,9 +120,12 @@ class PagedReaderState( readingDirection = readingDirection.value, ) val containerSize = screenScaleState.areaSize.value - val maxPageSize = getMaxPageSize(spread.pages.map { it.metadata }, containerSize) + val pagesMeta = spread.pages.map { it.metadata } + val gtcApplied = pagesMeta.singleOrNull()?.let { isGtcApplicable(it, containerSize) } ?: false + val maxPageSize = getMaxPageSize(pagesMeta, gtcAdjustedContainerSize(pagesMeta, containerSize)) val targetSize = fitToScreenZoom(spread.pages, maxPageSize, layout.value) - screenScaleState.setTargetSize(targetSize.toSize()) + val targetSizeForScale = if (gtcApplied) IntSize(targetSize.height, targetSize.width) else targetSize + screenScaleState.setTargetSize(targetSizeForScale.toSize()) delay(100) } .launchIn(stateScope) @@ -144,7 +149,9 @@ class PagedReaderState( screenScaleState: ScreenScaleState, readingDirection: PagedReadingDirection, ) { - val maxPageSize = getMaxPageSize(spread.pages.map { it.metadata }, screenScaleState.areaSize.value) + val containerSize = screenScaleState.areaSize.value + val pagesMeta = spread.pages.map { it.metadata } + val maxPageSize = getMaxPageSize(pagesMeta, gtcAdjustedContainerSize(pagesMeta, containerSize)) val zoomFactor = screenScaleState.transformation.value.scale val offset = screenScaleState.transformation.value.offset val areaSize = screenScaleState.areaSize.value.toSize() @@ -156,6 +163,20 @@ class PagedReaderState( pages.forEachIndexed { index, result -> if (result.imageResult is ReaderImageResult.Success) { val image = result.imageResult.image + + if (isGtcApplicable(result.metadata, containerSize)) { + // GTC: page is rotated 90 degrees for display, so pan/zoom offsets + // (computed in unrotated screen space) don't map cleanly onto it. + // Always display the whole image scaled to fit instead. + val imageDisplaySize = image.calculateSizeForArea(maxPageSize, stretchToFit) ?: maxPageSize + image.requestUpdate( + visibleDisplaySize = IntRect(0, 0, imageDisplaySize.width, imageDisplaySize.height), + zoomFactor = 1f, + maxDisplaySize = maxPageSize + ) + return@forEachIndexed + } + val imageDisplaySize = image.calculateSizeForArea(maxPageSize, stretchToFit)?:maxPageSize val imageHorizontalVisibleWidth = @@ -360,14 +381,17 @@ class PagedReaderState( private suspend fun completeLoadJob(pages: List): PagesLoadJob { val containerSize = screenScaleState.areaSize.value - val maxPageSize = getMaxPageSize(pages.map { it.metadata }, containerSize) + val pagesMeta = pages.map { it.metadata } + val maxPageSize = getMaxPageSize(pagesMeta, gtcAdjustedContainerSize(pagesMeta, containerSize)) + val gtcRotationApplied = pagesMeta.singleOrNull()?.let { isGtcApplicable(it, containerSize) } ?: false val newScale = calculateScreenScale( pages, areaSize = containerSize, maxPageSize = maxPageSize, scaleType = scaleType.value, displayLayout = layout.value, - stretchToFit = readerState.imageStretchToFit.value + stretchToFit = readerState.imageStretchToFit.value, + gtcRotationApplied = gtcRotationApplied, ) val spread = PageSpread(pages) when (readingDirection.value) { @@ -526,6 +550,32 @@ class PagedReaderState( onScaleTypeChange(newScale) } + fun onGtcModeEnabledChange(enabled: Boolean) { + this.gtcModeEnabled.value = enabled + val currentPage = currentSpread.value.pages.firstOrNull()?.metadata + if (currentPage != null) loadPage(spreadIndexOf(currentPage)) + stateScope.launch { settingsRepository.putGtcModeEnabled(enabled) } + } + + /** + * GTC (device-height orientation): when enabled, a landscape page (its longest side + * horizontal) viewed on a portrait device is rotated so its longest side aligns with + * the device height, and the image is scaled to fit before display. + * Only applies to single page layout - rotating a two-page spread isn't well-defined. + */ + private fun isGtcApplicable(pageMetadata: PageMetadata, containerSize: IntSize): Boolean { + return gtcModeEnabled.value && + layout.value == SINGLE_PAGE && + pageMetadata.isLandscape() && + containerSize.height > containerSize.width + } + + private fun gtcAdjustedContainerSize(pages: List, containerSize: IntSize): IntSize { + val page = pages.singleOrNull() ?: return containerSize + return if (isGtcApplicable(page, containerSize)) IntSize(containerSize.height, containerSize.width) + else containerSize + } + fun onReadingDirectionChange(readingDirection: PagedReadingDirection) { this.readingDirection.value = readingDirection stateScope.launch { settingsRepository.putPagedReaderReadingDirection(readingDirection) } @@ -538,11 +588,17 @@ class PagedReaderState( scaleType: LayoutScaleType, displayLayout: PageDisplayLayout, stretchToFit: Boolean, + gtcRotationApplied: Boolean = false, ): ScreenScaleState { val scaleState = ScreenScaleState() scaleState.setAreaSize(areaSize) val fitToScreenSize = fitToScreenZoom(pages, maxPageSize, displayLayout) + if (gtcRotationApplied) { + GtcReaderSupport.applyFixedScale(scaleState, fitToScreenSize) + return scaleState + } + scaleState.setTargetSize(fitToScreenSize.toSize()) val actualSpreadSize = pages.map { diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/BottomSheetSettingsOverlay.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/BottomSheetSettingsOverlay.kt index e486fc9b1..116392c09 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/BottomSheetSettingsOverlay.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/BottomSheetSettingsOverlay.kt @@ -385,6 +385,14 @@ private fun PagedModeSettings( ) } + val gtcModeEnabled = pageState.gtcModeEnabled.collectAsState().value + SwitchWithLabel( + checked = gtcModeEnabled, + onCheckedChange = pageState::onGtcModeEnabledChange, + label = { Text("GTC") }, + supportingText = { Text("Rotate wide pages to fill device height, then scale to fit") }, + contentPadding = PaddingValues(horizontal = 10.dp), + ) } } diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/SettingsSideMenu.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/SettingsSideMenu.kt index 59ee117c4..8a3c4ed49 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/SettingsSideMenu.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/SettingsSideMenu.kt @@ -424,6 +424,15 @@ private fun ColumnScope.PagedReaderSettingsContent( contentPadding = PaddingValues(horizontal = 10.dp) ) } + + val gtcModeEnabled = pageState.gtcModeEnabled.collectAsState().value + SwitchWithLabel( + checked = gtcModeEnabled, + onCheckedChange = pageState::onGtcModeEnabledChange, + label = { Text("GTC") }, + supportingText = { Text("Rotate wide pages to fill device height, then scale to fit") }, + contentPadding = PaddingValues(horizontal = 10.dp) + ) } } diff --git a/komelia-ui/src/jvmMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.jvm.kt b/komelia-ui/src/jvmMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.jvm.kt new file mode 100755 index 000000000..94a2bc15a --- /dev/null +++ b/komelia-ui/src/jvmMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.jvm.kt @@ -0,0 +1,6 @@ +package snd.komelia.ui.platform + +import androidx.compose.runtime.Composable + +@Composable +actual fun LockScreenOrientation(locked: Boolean) = Unit diff --git a/komelia-ui/src/wasmJsMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.wasmJs.kt b/komelia-ui/src/wasmJsMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.wasmJs.kt new file mode 100755 index 000000000..94a2bc15a --- /dev/null +++ b/komelia-ui/src/wasmJsMain/kotlin/snd/komelia/ui/platform/ScreenOrientationLock.wasmJs.kt @@ -0,0 +1,6 @@ +package snd.komelia.ui.platform + +import androidx.compose.runtime.Composable + +@Composable +actual fun LockScreenOrientation(locked: Boolean) = Unit