diff --git a/.gitignore b/.gitignore index 11b4829..4852434 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ .DS_Store /build /captures -/app/release +/app-sample/release /keystoreDetails .externalNativeBuild .cxx diff --git a/README.md b/README.md index 1a9f752..029dfbf 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@ -# QuickEdit - Photo Editor -QuickEdit is a user-friendly photo editor for Android, built using **Jetpack Compose**. It offers essential photo editing tools with a clean and smooth interface. - -## Latest Release - -[![Release v1.1.0](https://img.shields.io/github/v/release/Abizer-R/QuickEdit-Photo-Editor)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/tag/v1.1.0-4) - -- [Download from the Google Play Store](https://play.google.com/store/apps/details?id=com.abizer_r.quickedit) -- [Download Apk (v1.1.0)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/download/v1.1.0-4/app-release.apk) - - -## [Click Here To Watch Demo Video](https://drive.google.com/file/d/18IipYR_jbUQVFL8U1jNEJd_KTm_Y9ije/view?usp=sharing) +## ⚠️ Partial Readme (Will be updated after draw tool is completed) +--- +# QuickEdit - Photo Editor (Compose, Modular, Pluggable) +[![Release](https://img.shields.io/github/v/release/Abizer-R/QuickEdit-Photo-Editor)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases) +[![Min SDK](https://img.shields.io/badge/minSdk-24-blue)](#) +[![Target SDK](https://img.shields.io/badge/targetSdk-35-blue)](#) +[![License](https://img.shields.io/badge/license-Apache--2.0-green)](#license) +**QuickEdit** is a modular, pluggable photo editor for Android with a **stable core engine**, a **Compose UI shell**, and **tool plugins** (Crop ✅, Draw/Text/Effects scaffolds). +It preserves legacy UX (slide-out editor bars → full-screen tools) while cleaning layering, performance, and testability. +- 📱 **Play Store:** [Install](https://play.google.com/store/apps/details?id=com.abizer_r.quickedit) +- 📦 **Latest APK:** [Releases](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases)
1 @@ -35,16 +34,61 @@ QuickEdit is a user-friendly photo editor for Android, built using **Jetpack Com - **Add Text**: Add text with option to customize fonts and format (bold, italic and more). - **Smooth Animations**: Enjoy a seamless experience thanks to Jetpack Compose. +--- + +## Architecture (v0.x) + +**Goal:** a reusable editor published as Maven artifacts with a stable engine API and pluggable tools. +**Status:** engine + shell done; Crop tool delivered; Draw in progress; Effects & Text planned. + +## Modules & Dependency Rules + +``` +app-sample/ # Host app; exercises the library +quickedit/ # Legacy façade (keeps old entrypoints stable) +quickedit-core-engine/ # Engine API + default impl (no Compose/UI deps) +quickedit-compose-ui/ # Shell + tool contracts (Compose UI, no tool logic) +quickedit-tool-crop/ # Crop tool (full-screen) +quickedit-tool-draw/ # (ongoing) +quickedit-tool-text/ # (planned) +quickedit-tool-effects/ # (planned) +``` + +### **Dependency graph (enforced):** +``` +app-sample ──► quickedit (legacy) + ├──► quickedit-compose-ui ──api──► quickedit-core-engine + └──► quickedit-tool-* +quickedit-tool-* ──► quickedit-compose-ui +quickedit-core-engine (Android framework only; no UI) +``` + +**Why `api(..)` from UI → Engine?** The UI public API mentions engine types (`EditImage`, `SaveFormat`). Re-exporting via `api(project(":quickedit-core-engine"))` makes those types visible to consumers cleanly. + + +## Tech Stack & Versions (from `libs.versions.toml`) + +- **Android:** minSdk 24 · target/compile 35 · JDK 17 +- **Gradle/AGP:** 8.6.1 +- **Kotlin:** 2.1.10 (+ Compose plugin) +- **Compose BOM:** 2025.05.00 +- **Compose libs:** Material3, Animation `1.7.0-beta04`, UI/Test/Tooling +- **AndroidX:** Activity Compose 1.9.0 · Lifecycle 2.8.2 · Navigation 2.7.7 · AppCompat 1.7.0 · Core-ktx 1.13.1 +- **DI:** Hilt 2.57.1 (with hilt-navigation-compose 1.2.0) +- **Coroutines:** 1.10.1 +- **Imaging:** GPUImage 2.1.0 · CanHub Cropper 4.5.0 · Compose-Screenshot 1.0.3 +- **UX Utils:** Cloudy 0.2.7 · ColorPicker 1.0.0 +- **Testing:** JUnit 4.13.2 · AndroidX JUnit 1.2.0 · Espresso 3.6.0 + +--- + ## Libraries Used QuickEdit makes use of the following libraries to provide its features: - **Jetpack Compose**: A modern toolkit for building native Android UI. - **Compose Animations**: For smooth and customizable UI animations. -- **[GPUImage](https://github.com/CyberAgent/android-gpuimage)**: A library for GPU-based image processing by CyberAgent. -- **[Cloudy](https://github.com/skydoves/cloudy)**: A library by Skydoves for blurring a composable. - **[Image Cropper](https://github.com/CanHub/Android-Image-Cropper)**: A cropping library by Canhub that allows users to crop images seamlessly. -- **[Compose-Screenshot](https://github.com/SmartToolFactory/Compose-Screenshot)**: A library by SmartToolFactory for capturing screenshots of composables in Jetpack Compose. # License diff --git a/app/.gitignore b/app-sample/.gitignore similarity index 100% rename from app/.gitignore rename to app-sample/.gitignore diff --git a/app/build.gradle.kts b/app-sample/build.gradle.kts similarity index 100% rename from app/build.gradle.kts rename to app-sample/build.gradle.kts diff --git a/app/proguard-rules.pro b/app-sample/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to app-sample/proguard-rules.pro diff --git a/app/src/androidTest/java/com/abizer_r/sketchdraft/ExampleInstrumentedTest.kt b/app-sample/src/androidTest/java/com/abizer_r/sketchdraft/ExampleInstrumentedTest.kt similarity index 100% rename from app/src/androidTest/java/com/abizer_r/sketchdraft/ExampleInstrumentedTest.kt rename to app-sample/src/androidTest/java/com/abizer_r/sketchdraft/ExampleInstrumentedTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app-sample/src/main/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to app-sample/src/main/AndroidManifest.xml diff --git a/app/src/main/ic_launcher-playstore.png b/app-sample/src/main/ic_launcher-playstore.png similarity index 100% rename from app/src/main/ic_launcher-playstore.png rename to app-sample/src/main/ic_launcher-playstore.png diff --git a/app/src/main/java/com/abizer_r/quickedit/ui/QuickEditApplication.kt b/app-sample/src/main/java/com/abizer_r/quickedit/ui/QuickEditApplication.kt similarity index 100% rename from app/src/main/java/com/abizer_r/quickedit/ui/QuickEditApplication.kt rename to app-sample/src/main/java/com/abizer_r/quickedit/ui/QuickEditApplication.kt diff --git a/app/src/main/java/com/abizer_r/quickedit/ui/main/MainActivity.kt b/app-sample/src/main/java/com/abizer_r/quickedit/ui/main/MainActivity.kt similarity index 100% rename from app/src/main/java/com/abizer_r/quickedit/ui/main/MainActivity.kt rename to app-sample/src/main/java/com/abizer_r/quickedit/ui/main/MainActivity.kt diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app-sample/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_background.xml rename to app-sample/src/main/res/drawable/ic_launcher_background.xml diff --git a/app/src/main/res/drawable/ic_launcher_background2.xml b/app-sample/src/main/res/drawable/ic_launcher_background2.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_background2.xml rename to app-sample/src/main/res/drawable/ic_launcher_background2.xml diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app-sample/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_foreground.xml rename to app-sample/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app-sample/src/main/res/mipmap-hdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher.webp rename to app-sample/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp b/app-sample/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp rename to app-sample/src/main/res/mipmap-hdpi/ic_launcher_foreground2.webp diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app-sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher_round.webp rename to app-sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app-sample/src/main/res/mipmap-mdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher.webp rename to app-sample/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp b/app-sample/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp rename to app-sample/src/main/res/mipmap-mdpi/ic_launcher_foreground2.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app-sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher_round.webp rename to app-sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app-sample/src/main/res/mipmap-xhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher.webp rename to app-sample/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp b/app-sample/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp rename to app-sample/src/main/res/mipmap-xhdpi/ic_launcher_foreground2.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app-sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp rename to app-sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app-sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.webp rename to app-sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp b/app-sample/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp rename to app-sample/src/main/res/mipmap-xxhdpi/ic_launcher_foreground2.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app-sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp rename to app-sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp rename to app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp b/app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp rename to app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground2.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp rename to app-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/values/colors.xml b/app-sample/src/main/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to app-sample/src/main/res/values/colors.xml diff --git a/app/src/main/res/values/strings.xml b/app-sample/src/main/res/values/strings.xml similarity index 100% rename from app/src/main/res/values/strings.xml rename to app-sample/src/main/res/values/strings.xml diff --git a/app/src/main/res/values/themes.xml b/app-sample/src/main/res/values/themes.xml similarity index 100% rename from app/src/main/res/values/themes.xml rename to app-sample/src/main/res/values/themes.xml diff --git a/app/src/main/res/xml/backup_rules.xml b/app-sample/src/main/res/xml/backup_rules.xml similarity index 100% rename from app/src/main/res/xml/backup_rules.xml rename to app-sample/src/main/res/xml/backup_rules.xml diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app-sample/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from app/src/main/res/xml/data_extraction_rules.xml rename to app-sample/src/main/res/xml/data_extraction_rules.xml diff --git a/app/src/test/java/com/abizer_r/quickedit/ExampleUnitTest.kt b/app-sample/src/test/java/com/abizer_r/quickedit/ExampleUnitTest.kt similarity index 100% rename from app/src/test/java/com/abizer_r/quickedit/ExampleUnitTest.kt rename to app-sample/src/test/java/com/abizer_r/quickedit/ExampleUnitTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 905f308..2595be9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ lifecycleRuntimeKtx = "2.8.2" material = "1.12.0" navigationCompose = "2.7.7" composeAnimation = "1.7.0-beta04" +coroutines = "1.10.1" [libraries] @@ -53,6 +54,8 @@ hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltA hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroid" } junit = { module = "junit:junit", version.ref = "junit" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } material = { module = "com.google.android.material:material", version.ref = "material" } [plugins] diff --git a/quickedit-core-engine/.gitignore b/quickedit-core-engine/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-core-engine/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-core-engine/build.gradle.kts b/quickedit-core-engine/build.gradle.kts new file mode 100644 index 0000000..164080d --- /dev/null +++ b/quickedit-core-engine/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "io.github.abizerr.quickedit.engine" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.kotlinx.coroutines.core) +// implementation(libs.androidx.appcompat) +// implementation(libs.material) +// testImplementation(libs.junit) +// androidTestImplementation(libs.androidx.junit) +// androidTestImplementation(libs.androidx.espresso.core) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/quickedit-core-engine/consumer-rules.pro b/quickedit-core-engine/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-core-engine/proguard-rules.pro b/quickedit-core-engine/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-core-engine/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-core-engine/src/androidTest/java/io/github/abizerr/quickedit/engine/ExampleInstrumentedTest.kt b/quickedit-core-engine/src/androidTest/java/io/github/abizerr/quickedit/engine/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..a2498fe --- /dev/null +++ b/quickedit-core-engine/src/androidTest/java/io/github/abizerr/quickedit/engine/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.engine + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.engine.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-core-engine/src/main/AndroidManifest.xml b/quickedit-core-engine/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b67b168 --- /dev/null +++ b/quickedit-core-engine/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditEngine.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditEngine.kt new file mode 100644 index 0000000..1be5c6a --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditEngine.kt @@ -0,0 +1,37 @@ +package io.github.abizerr.quickedit.engine.api + +interface EditEngine { + suspend fun newSession(image: EditImage): EditSnapshot + suspend fun apply(op: EditOp): EditSnapshot + suspend fun render(snapshot: EditSnapshot, size: Size): RenderResult + suspend fun save(snapshot: EditSnapshot, format: SaveFormat): Result + val history: HistoryView + /** + * FUTURE-1: Capabilities & queries (non-breaking): + * fun supports(op: kotlin.reflect.KClass): Boolean = true + * val maxCanvasSize: Size get() = Size(8192, 8192) + * FUTURE-2: Observability hooks: + * var events: ((EngineEvent) -> Unit)? + */ +} + +interface HistoryView { + val canUndo: Boolean + val canRedo: Boolean + val undoCount: Int + val redoCount: Int + /** + * FUTURE: Expose memory footprint if required + * val approxMemoryBytes: Long get() = 0 + */ +} + + +/** + * FUTURE: Emit non-fatal diagnostics without throwing: + * sealed interface EngineEvent { + * data class OpLatency(val op: EditOp, val ms: Long): EngineEvent + * data class RenderDownscaled(val requested: Size, val actual: Size): EngineEvent + * data class OOMPrevented(val reducedHistory: Int): EngineEvent + * } + */ \ No newline at end of file diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditModels.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditModels.kt new file mode 100644 index 0000000..b9aa89b --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditModels.kt @@ -0,0 +1,48 @@ +package io.github.abizerr.quickedit.engine.api + +import android.graphics.Bitmap + + +/** A lightweight reference to the image the editor works on. */ +sealed interface EditImage { + data class FromUri(val uri: android.net.Uri) : EditImage + data class FromBitmap(val bitmap: android.graphics.Bitmap) : EditImage + /** + * FUTURE: Possible additions without breaking callers: + * example: FromBytes, FromFile, FromHardwareBuffer, ContentProvider, etc. + */ +} + +/** Immutable snapshot of the editing graph at a point in time. */ +data class EditSnapshot( + val image: EditImage, + val rev: Long = 0L, + val operations: List = emptyList() + /** + * FUTURE: Add graph states without breaking callers: + * example: Layers, Selection, Metadata + */ +) + +/** Output from save(). You can extend later with Uri/bytes/metadata. */ +data class EditedImage( + val mimeType: String, + val bytes: ByteArray? = null + /** + * FUTURE: Add destination/EXIF without breaking callers: + * example: savedFile: android.net.Uri, exif: Map + */ +) + +/** Sizes used by render() */ +data class Size(val width: Int, val height: Int) + +/** Render output placeholder (Phase 5 will hold a Bitmap or ImageBitmap). */ +data class RenderResult( + val ok: Boolean, + val preview: Bitmap? +) +/** + * FUTURE: Add preview payloads without chaning the signature of render() in EditEngine interface: + * example: bitmap, downscaleFactor, etc. + */ \ No newline at end of file diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditOp.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditOp.kt new file mode 100644 index 0000000..7f07fc5 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/EditOp.kt @@ -0,0 +1,16 @@ +package io.github.abizerr.quickedit.engine.api + +import android.graphics.Rect +import android.graphics.RectF +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec + +/** Minimum set; expand in Phase 6 with tool packs. */ +sealed interface EditOp { +// data class ApplyCurve(val presetId: String) : EditOp + data class CropImage(val rect: Rect) : EditOp +// data class InsertText(val text: String, val x: Float, val y: Float) : EditOp + data object Undo : EditOp + data object Redo : EditOp + + data class DrawShape(val spec: ShapeSpec) +} diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/SaveFormat.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/SaveFormat.kt new file mode 100644 index 0000000..7d405dd --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/api/SaveFormat.kt @@ -0,0 +1,11 @@ +package io.github.abizerr.quickedit.engine.api + +sealed interface SaveFormat { + data class Png(val lossless: Boolean = true) : SaveFormat + data class Jpeg(val quality: Int = 90) : SaveFormat + data class WebP(val lossless: Boolean = false, val quality: Int = 90) : SaveFormat + /** + * FUTURE: Add new Codecs additively + * example: Heif, Avif + */ +} diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/PaintSpec.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/PaintSpec.kt new file mode 100644 index 0000000..8c25a87 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/PaintSpec.kt @@ -0,0 +1,15 @@ +package io.github.abizerr.quickedit.engine.drawspec + +data class PaintSpec( + val argb: Int, + val alpha: Float, + val widthPx: Float, + val isEraser: Boolean +) + +fun ShapeSpec.toPaintSpec(): PaintSpec = PaintSpec( + argb = this.argb, + alpha = this.alpha.coerceIn(0f,1f), + widthPx = this.widthPx.coerceAtLeast(0.5f), + isEraser = this.isEraser +) \ No newline at end of file diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeSpec.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeSpec.kt new file mode 100644 index 0000000..8b51e7d --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeSpec.kt @@ -0,0 +1,31 @@ +package io.github.abizerr.quickedit.engine.drawspec + +import androidx.annotation.FloatRange + +sealed interface ShapeSpec { + + companion object { + val defaultOffset = Pair(Float.NaN, Float.NaN) + } + val argb: Int + val alpha: Float // 0f..1f + val widthPx: Float + val isEraser: Boolean get() = false + + data class Brush( + val points: MutableList> = mutableListOf(), + override val argb: Int, + @FloatRange(from = 0.0, to = 1.0) override val alpha: Float, + override val widthPx: Float, + override val isEraser: Boolean = false + ) : ShapeSpec + + data class Shape( + val shapeType: ShapeType, + var startOffset: Pair = defaultOffset, + var endOffset: Pair = defaultOffset, + override val argb: Int, + @FloatRange(from = 0.0, to = 1.0) override val alpha: Float, + override val widthPx: Float, + ) : ShapeSpec +} diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeType.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeType.kt new file mode 100644 index 0000000..b8724a3 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/drawspec/ShapeType.kt @@ -0,0 +1,5 @@ +package io.github.abizerr.quickedit.engine.drawspec + +enum class ShapeType { + LINE, OVAL, RECTANGLE +} \ No newline at end of file diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/DefaultEditEngine.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/DefaultEditEngine.kt new file mode 100644 index 0000000..02ee004 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/DefaultEditEngine.kt @@ -0,0 +1,175 @@ +package io.github.abizerr.quickedit.engine.impl + +import android.content.ContentResolver +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.ImageDecoder +import android.graphics.Paint +import android.graphics.Rect +import android.os.Build +import io.github.abizerr.quickedit.engine.api.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import androidx.core.graphics.scale +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec +import io.github.abizerr.quickedit.engine.util.applySpec +import io.github.abizerr.quickedit.engine.util.drawOn +import java.io.ByteArrayOutputStream + +/** + * Minimal but real engine: + * - Decodes source (Uri/Bitmap) + * - Renders a scaled preview Bitmap + * - Saves to bytes (PNG/JPEG/WebP) + */ +class DefaultEditEngine( + private val maxUndo: Int = 20, + private val resolver: ContentResolver? = null +) : EditEngine { + + private val historyManager = HistoryManager(maxUndo) + + override val history: HistoryView = object : HistoryView { + override val canUndo: Boolean get() = this@DefaultEditEngine.historyManager.canUndo() + override val canRedo: Boolean get() = this@DefaultEditEngine.historyManager.canRedo() + override val undoCount: Int get() = this@DefaultEditEngine.historyManager.undoCount() + override val redoCount: Int get() = this@DefaultEditEngine.historyManager.redoCount() + } + + override suspend fun newSession(image: EditImage): EditSnapshot { + val snap = EditSnapshot(image = image, rev = 0) + historyManager.setInitial(snap) + return snap + } + + override suspend fun apply(op: EditOp): EditSnapshot = withContext(Dispatchers.Default) { + val current = historyManager.current() ?: error("Call newSession() first") + val next = when (op) { + is EditOp.Undo -> historyManager.undo() ?: current + is EditOp.Redo -> historyManager.redo() ?: current + + is EditOp.CropImage -> { + val baseBitmap = decode(current.image) ?: return@withContext current + val croppedBitmap = cropBitmapImageSpace(baseBitmap, op.rect) + current.copy( + image = EditImage.FromBitmap(croppedBitmap), + rev = current.rev + 1 + ) + } + + is EditOp.DrawShape -> { + current.copy( + rev = current.rev + 1, + operations = current.operations + op + ) + } + + else -> current.copy(rev = current.rev + 1) // TODO (revamp): placeholder; real ops later + } + if (op !is EditOp.Undo && op !is EditOp.Redo) historyManager.push(next) + return@withContext next + } + + override suspend fun render(snapshot: EditSnapshot, size: Size): RenderResult { + val bitmap = withContext(Dispatchers.Default) { + val baseBitmap = decode(snapshot.image) ?: return@withContext null + val scaledBitmap = scaleToFit(baseBitmap, size.width, size.height) + + // draw operations on top + val mutable = scaledBitmap.copy(Bitmap.Config.ARGB_8888, true) + val canvas = Canvas(mutable) + snapshot.operations.forEach { op -> + when (op) { + is EditOp.DrawShape -> { + val paintSpec = op.spec.toPaintSpec() + val paint = Paint().applySpec(paintSpec) + op.spec.drawOn(canvas, paint) + } + else -> Unit + } + + } + mutable + } ?: return RenderResult(ok = false, preview = null) + + return RenderResult(ok = true, preview = bitmap) + } + + override suspend fun save(snapshot: EditSnapshot, format: SaveFormat): Result = withContext(Dispatchers.IO) { + val baseBitmap = decode(snapshot.image) + ?: return@withContext Result.failure(IllegalStateException("Decode failed")) + + val (compressFormat, quality, mime) = when (format) { + is SaveFormat.Png -> Triple(Bitmap.CompressFormat.PNG, 100, "image/png") + is SaveFormat.Jpeg -> Triple(Bitmap.CompressFormat.JPEG, format.quality.coerceIn(0, 100), "image/jpeg") + is SaveFormat.WebP -> { + val q = format.quality.coerceIn(0, 100) + val mimeType = "image/webp" + val compress = if (Build.VERSION.SDK_INT >= 30) { + if (format.lossless) Bitmap.CompressFormat.WEBP_LOSSLESS else Bitmap.CompressFormat.WEBP_LOSSY + } else { + Bitmap.CompressFormat.WEBP + } + Triple(compress, q, mimeType) + } + } + + val outputStream = ByteArrayOutputStream() + baseBitmap.compress(compressFormat, quality, outputStream) + Result.success(EditedImage( + mimeType = mime, + bytes = outputStream.toByteArray() + )) + } + + private fun decode(image: EditImage): Bitmap? { + return when (image) { + is EditImage.FromBitmap -> image.bitmap + is EditImage.FromUri -> { + decodeBitmapFromUri(image) + } + } + } + + private fun decodeBitmapFromUri( + image: EditImage.FromUri + ): Bitmap? = try { + val contentResolver = resolver ?: return null + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // ImageDecoder handles EXIF orientation, wide color, scaling hints + val src = ImageDecoder.createSource(contentResolver, image.uri) + ImageDecoder.decodeBitmap(src) + } else { + // BitmapFactory.decodeStream doesn't auto-rotate based on EXIF + // But, it is the only safe option we have below API-P + contentResolver.openInputStream(image.uri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + } catch (e: Exception) { + e.printStackTrace() + null + } + + private fun scaleToFit(src: Bitmap, targetW: Int, targetH: Int): Bitmap { + if (targetW <= 0 || targetH <= 0) return src + val wScale = targetW.toFloat() / src.width + val hScale = targetH.toFloat() / src.height + val scale = minOf(wScale, hScale) + val w = (src.width * scale).toInt().coerceAtLeast(1) + val h = (src.height * scale).toInt().coerceAtLeast(1) + return src.scale(w, h) + } + + private fun cropBitmapImageSpace(src: Bitmap, rect: Rect): Bitmap { + val left = rect.left.coerceIn(0, src.width) + val right = rect.right.coerceIn(left, src.width) + val top = rect.top.coerceIn(0, src.height) + val bottom = rect.bottom.coerceIn(top, src.height) + val width = (right - left).coerceAtLeast(1) + val height = (bottom - top).coerceAtLeast(1) + return Bitmap.createBitmap(src, left, top, width, height) + } +} diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/HistoryManager.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/HistoryManager.kt new file mode 100644 index 0000000..af05ae5 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/impl/HistoryManager.kt @@ -0,0 +1,45 @@ +package io.github.abizerr.quickedit.engine.impl + +import io.github.abizerr.quickedit.engine.api.EditSnapshot + +internal class HistoryManager(maxUndo: Int) { + private val cap = maxUndo.coerceAtLeast(0) + private val past = ArrayDeque() + private var present: EditSnapshot? = null + private val future = ArrayDeque() + + fun setInitial(snapshot: EditSnapshot) { + present = snapshot; past.clear(); future.clear() + } + + fun push(next: EditSnapshot) { + present?.let { past.addLast(it); if (past.size > cap) past.removeFirst() } + present = next; future.clear() + } + + fun undo(): EditSnapshot? { + val prev = past.removeLastOrNull() ?: return null + present?.let { future.addLast(it) } + present = prev + return present + } + + fun redo(): EditSnapshot? { + val next = future.removeLastOrNull() ?: return null + present?.let { past.addLast(it); if (past.size > cap) past.removeFirst() } + present = next + return present + } + + fun current(): EditSnapshot? = present + fun canUndo() = past.isNotEmpty() + fun canRedo() = future.isNotEmpty() + fun undoCount() = past.size + fun redoCount() = future.size + + /** + * FUTURE: Add cap management strategy: + * - Drop oldest diffs + * - Merge consecutive strokes (from draw tool) into one snapshot + */ +} diff --git a/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/util/Extensions.kt b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/util/Extensions.kt new file mode 100644 index 0000000..d997d92 --- /dev/null +++ b/quickedit-core-engine/src/main/java/io/github/abizerr/quickedit/engine/util/Extensions.kt @@ -0,0 +1,73 @@ +package io.github.abizerr.quickedit.engine.util + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import io.github.abizerr.quickedit.engine.drawspec.PaintSpec +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.ShapeType +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec + +fun Paint.applySpec(paintSpec: PaintSpec): Paint = this.apply { + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + strokeJoin = Paint.Join.ROUND + strokeWidth = paintSpec.widthPx + color = paintSpec.argb + alpha = (paintSpec.alpha * 255f).toInt() + xfermode = if (paintSpec.isEraser) PorterDuffXfermode(PorterDuff.Mode.CLEAR) else null +} + +fun ShapeSpec.drawOn(canvas: Canvas, paint: Paint) { + val ps = toPaintSpec() + paint.applySpec(ps) + when (this) { + is ShapeSpec.Brush -> { + val path = android.graphics.Path() + var last: Pair? = null + for (p in points) { + if (last == null) { + path.moveTo(p.first, p.second) + } else { + path.quadTo( + last.first, + last.second, + (last.first + p.first) / 2, + (last.second + p.second) / 2, + ) + } + last = p + } + canvas.drawPath(path, paint) + } + + is ShapeSpec.Shape -> { + when (this.shapeType) { + ShapeType.LINE -> canvas.drawLine( + startOffset.first, + startOffset.second, + endOffset.first, + endOffset.second, + paint + ) + + ShapeType.OVAL -> canvas.drawOval( + startOffset.first, + startOffset.second, + endOffset.first, + endOffset.second, + paint + ) + ShapeType.RECTANGLE -> canvas.drawRect( + startOffset.first, + startOffset.second, + endOffset.first, + endOffset.second, + paint + ) + } + } + } +} \ No newline at end of file diff --git a/quickedit-core-engine/src/test/java/io/github/abizerr/quickedit/engine/ExampleUnitTest.kt b/quickedit-core-engine/src/test/java/io/github/abizerr/quickedit/engine/ExampleUnitTest.kt new file mode 100644 index 0000000..3e9d250 --- /dev/null +++ b/quickedit-core-engine/src/test/java/io/github/abizerr/quickedit/engine/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.engine + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit-tool-crop/.gitignore b/quickedit-tool-crop/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-tool-crop/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-tool-crop/build.gradle.kts b/quickedit-tool-crop/build.gradle.kts new file mode 100644 index 0000000..64a0f5a --- /dev/null +++ b/quickedit-tool-crop/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.github.abizerr.quickedit.tool.crop" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(project(":quickedit-ui")) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui.tooling.preview) + debugImplementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.material.icons.extended) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + implementation(libs.android.image.cropper) // canhub cropper +} \ No newline at end of file diff --git a/quickedit-tool-crop/consumer-rules.pro b/quickedit-tool-crop/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-tool-crop/proguard-rules.pro b/quickedit-tool-crop/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-tool-crop/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-tool-crop/src/androidTest/java/io/github/abizerr/quickedit/tool/crop/ExampleInstrumentedTest.kt b/quickedit-tool-crop/src/androidTest/java/io/github/abizerr/quickedit/tool/crop/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..4b2fa96 --- /dev/null +++ b/quickedit-tool-crop/src/androidTest/java/io/github/abizerr/quickedit/tool/crop/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.tool.crop + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.tool.crop.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-tool-crop/src/main/AndroidManifest.xml b/quickedit-tool-crop/src/main/AndroidManifest.xml new file mode 100644 index 0000000..989d6cc --- /dev/null +++ b/quickedit-tool-crop/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/AspectRatioDialog.kt similarity index 89% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt rename to quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/AspectRatioDialog.kt index b881fa3..ab22afc 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/crop/AspectRatioDialog.kt +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/AspectRatioDialog.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.ui.common.crop +package io.github.abizerr.quickedit.tool.crop import android.content.res.Configuration import androidx.annotation.StringRes @@ -33,11 +33,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.DarkPanel -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.utils.defaultTextColor -import com.abizer_r.quickedit.utils.toast +import io.github.abizerr.quickedit.ui.theme.DarkPanel +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.utils.errorToast const val MIN_RATIO = 0.15f const val MAX_RATIO = 5.0f @@ -61,12 +59,8 @@ fun AspectRatioDialog( var aspectX by remember { mutableStateOf("1") } var aspectY by remember { mutableStateOf("1") } - val titleTextStyle = MaterialTheme.typography.titleMedium.copy( - color = defaultTextColor() - ) - val bodyTextStyle = MaterialTheme.typography.bodySmall.copy( - color = defaultTextColor() - ) + val titleTextStyle = MaterialTheme.typography.titleMedium + val bodyTextStyle = MaterialTheme.typography.bodySmall Box( modifier = Modifier.background( @@ -127,7 +121,7 @@ fun AspectRatioDialog( if (validation.isValid) { onSetRatio(aspectX.toInt(), aspectY.toInt()) } else { - context.toast(context.getString(validation.errorResId ?: R.string.something_went_wrong)) + context.errorToast(validation.errorResId) } } ) { @@ -189,11 +183,6 @@ private fun RatioInputField( OutlinedTextField( modifier = modifier, singleLine = true, -// value = TextFieldValue( -// text = text, -// selection = TextRange(text.length) -// ), -// onValueChange = onValueChange, value = text, onValueChange = { newValue -> // Allow only digits and empty value @@ -207,9 +196,9 @@ private fun RatioInputField( ) } -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview @Composable -fun PreviewAspectRatioDialog() { +private fun PreviewAspectRatioDialog() { QuickEditTheme { AspectRatioDialog( onDismiss = {}, diff --git a/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropContribution.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropContribution.kt new file mode 100644 index 0000000..a77825b --- /dev/null +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropContribution.kt @@ -0,0 +1,43 @@ +package io.github.abizerr.quickedit.tool.crop + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Crop +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolContribution +import io.github.abizerr.quickedit.ui.api.ToolController + +class CropContribution : ToolContribution { + override val id: String = "crop" + override val label: String = "Crop" + override val supportsFullScreen: Boolean get() = true + + @Composable + override fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) { + IconWithLabel( + selected = selected, + imageVector = Icons.Outlined.Crop, + labelText = label, + onClick = onClick + ) + } + + @Composable + override fun Panel(state: QuickEditState, controller: ToolController) { + Text("Crop tool placeholder") + } + + @Composable + override fun FullScreenTool( + state: QuickEditState, + controller: ToolController, + onExit: () -> Unit + ) { + CropToolScreen( + state = state, + controller = controller, + onExit = onExit + ) + } +} diff --git a/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropToolScreen.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropToolScreen.kt new file mode 100644 index 0000000..b442cba --- /dev/null +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropToolScreen.kt @@ -0,0 +1,320 @@ +package io.github.abizerr.quickedit.tool.crop + +import android.graphics.Rect +import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.canhub.cropper.CropImageOptions +import com.canhub.cropper.CropImageView +import io.github.abizerr.quickedit.engine.api.EditImage +import io.github.abizerr.quickedit.engine.api.EditOp +import io.github.abizerr.quickedit.tool.crop.model.CropperOption +import io.github.abizerr.quickedit.tool.crop.utils.CropModeUtils +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolController +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.TOOLBAR_HEIGHT_LARGE +import io.github.abizerr.quickedit.ui.common.TOOLBAR_HEIGHT_SMALL +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.PreviewUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils.TOOLBAR_COLLAPSE_ANIM_DURATION_FAST +import io.github.abizerr.quickedit.ui.utils.errorToast +import io.github.abizerr.quickedit.ui.utils.toast +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +private val topToolbarHeight = TOOLBAR_HEIGHT_SMALL +private val bottomToolbarHeight = TOOLBAR_HEIGHT_LARGE + +@Composable +fun CropToolScreen( + state: QuickEditState, + controller: ToolController, + onExit: () -> Unit +) { + val context = LocalContext.current + + var toolbarVisible by remember { mutableStateOf(true) } + val scope = rememberCoroutineScope() + + var cropView: CropImageView? by remember { mutableStateOf(null) } + var cropImageOptions by remember { mutableStateOf(CropImageOptions()) } + + val cropperOptionsList = remember { CropModeUtils.getCropperOptionsList() } + var selectedCropOptionIndex by remember { mutableIntStateOf(0) } + var showCropRatioDialog by remember { mutableStateOf(false) } + + fun emitCropAndExit(rect: Rect?, rotationDeg: Int = 0) { + if (rect == null) { + context.errorToast() + return + } + scope.launch { + controller.emit(EditOp.CropImage(rect)) + toolbarVisible = false + delay(AnimUtils.TOOLBAR_COLLAPSE_ANIM_DURATION_FAST.toLong()) + onExit() + } + } + + Box(Modifier.fillMaxSize()) { + TopToolbar( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth(), + visible = toolbarVisible, + height = topToolbarHeight, + onClose = { + scope.launch { + toolbarVisible = false + delay(TOOLBAR_COLLAPSE_ANIM_DURATION_FAST.toLong()) + onExit() + } + }, + onDone = { + val rect = cropView?.cropRect + emitCropAndExit(rect) + } + ) + + BottomToolbar( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + visible = toolbarVisible, + height = bottomToolbarHeight, + cropperOptionsList = cropperOptionsList, + selectedCropOptionIndex = selectedCropOptionIndex, + onCropOptionItemClicked = { position, cropOption -> + selectedCropOptionIndex = position + when (cropOption.aspectRatioX) { + -1f -> { + cropImageOptions = cropImageOptions.copy( + fixAspectRatio = false, + aspectRatioX = 1, + aspectRatioY = 1 + ) + } + -2f -> { + showCropRatioDialog = true + } + else -> { + cropImageOptions = cropImageOptions.copy( + fixAspectRatio = true, + aspectRatioX = cropOption.aspectRatioX.toInt(), + aspectRatioY = cropOption.aspectRatioY.toInt() + ) + } + } + } + ) + + Box( + Modifier + .fillMaxSize() + .padding(top = topToolbarHeight, bottom = bottomToolbarHeight) + ) { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + + // 1) Stable container the compose world will measure/layout + val container = android.widget.FrameLayout(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + clipToPadding = false + } + + // 2) The self-mutating library with its own onLayout() + // which triggers compose's measure causing recurring calls and ANR + // Adding this inside the stable container fixes the recurring calls and ANR + val mCropView = CropImageView(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + when (val img = state.snapshot.image) { + is EditImage.FromBitmap -> setImageBitmap(img.bitmap) + is EditImage.FromUri -> setImageUriAsync(img.uri) + } + setImageCropOptions(cropImageOptions) + } + + container.addView(mCropView) + cropView = mCropView + container + }, + update = { container -> + val mCropView = cropView ?: return@AndroidView + mCropView.setImageCropOptions(cropImageOptions) + } + ) + } + + AnimatedVisibility( + visible = showCropRatioDialog, + ) { + AspectRatioDialog( + onDismiss = { showCropRatioDialog = false }, + onSetRatio = { x, y -> + context.toast("x = $x, y = $x. r = ${x.toFloat() / y.toFloat()}") + selectedCropOptionIndex = cropperOptionsList.indexOfFirst { it.aspectRatioX == -2f } + cropImageOptions = cropImageOptions.copy( + fixAspectRatio = true, + aspectRatioX = x, + aspectRatioY = y + ) + showCropRatioDialog = false + } + ) + } + } + +} + +@Composable +private fun TopToolbar( + modifier: Modifier = Modifier, + visible: Boolean, + height: Dp, + onClose: () -> Unit, + onDone: () -> Unit +) { + AnimatedToolbarContainer( + toolbarVisible = visible, + modifier = modifier + ) { + Surface(tonalElevation = 2.dp) { + Row( + Modifier + .height(height) + .fillMaxSize() + .background(ToolBarBackgroundColor) + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + IconButton( + onClick = { onClose() }, + enabled = true + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.Default.Close, + contentDescription = "Close", + ) + } + Text( + text = "Crop", + style = MaterialTheme.typography.titleMedium + ) + IconButton( + onClick = { onDone() }, + enabled = true + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.Default.Check, + contentDescription = "Done", + ) + } + } + } + } +} + +@Composable +private fun BottomToolbar( + modifier: Modifier = Modifier, + visible: Boolean, + height: Dp, + cropperOptionsList: List, + selectedCropOptionIndex: Int, + onCropOptionItemClicked: (pos: Int, option: CropperOption) -> Unit, +) { + AnimatedToolbarContainer( + toolbarVisible = visible, + modifier = modifier + ) { + Surface(tonalElevation = 3.dp) { + CropperOptionsFullWidth( + modifier = Modifier, + toolbarHeight = height, + cropperOptionList = cropperOptionsList, + selectedIndex = selectedCropOptionIndex, + onItemClicked = onCropOptionItemClicked + ) + } + } +} + + +@Preview @Composable +private fun PreviewTopToolbar() { + QuickEditTheme { + TopToolbar( + visible = true, + height = topToolbarHeight, + onClose = {}, + onDone = {} + ) + } +} + +@Preview @Composable +private fun PreviewBottomToolbar() { + QuickEditTheme { + BottomToolbar( + visible = true, + height = bottomToolbarHeight, + cropperOptionsList = CropModeUtils.getCropperOptionsList(), + selectedCropOptionIndex = 0, + onCropOptionItemClicked = {_, _ -> } + ) + } +} + +@Preview @Composable +private fun PreviewCropToolScreen() { + QuickEditTheme { + CropToolScreen( + state = PreviewUtils.getDummyEditorState(), + controller = PreviewUtils.getDummyToolController(), + onExit = {} + ) + } +} diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropperOptionsFullWidth.kt similarity index 78% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt rename to quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropperOptionsFullWidth.kt index 47ea5de..d1f47b7 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOptionsFullWidth.kt +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/CropperOptionsFullWidth.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.ui.cropMode.cropperOptions +package io.github.abizerr.quickedit.tool.crop import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi @@ -7,6 +7,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -16,6 +17,9 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Crop +import androidx.compose.material.icons.filled.CropFree import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -31,18 +35,19 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_LARGE -import com.abizer_r.quickedit.utils.defaultTextColor -import com.abizer_r.quickedit.utils.editorScreen.CropModeUtils +import io.github.abizerr.quickedit.tool.crop.model.CropperOption +import io.github.abizerr.quickedit.tool.crop.utils.CropModeUtils +import io.github.abizerr.quickedit.ui.common.TOOLBAR_HEIGHT_LARGE +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.defaultTextColor @OptIn(ExperimentalFoundationApi::class) @Composable fun CropperOptionsFullWidth( modifier: Modifier = Modifier, toolbarHeight: Dp = TOOLBAR_HEIGHT_LARGE, - cropperOptionList: ArrayList, + cropperOptionList: List, selectedIndex: Int, onItemClicked: (position: Int, effectItem: CropperOption) -> Unit ) { @@ -51,8 +56,10 @@ fun CropperOptionsFullWidth( modifier = modifier .fillMaxWidth() .height(toolbarHeight) + .background(ToolBarBackgroundColor) .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + contentPadding = PaddingValues(horizontal = 16.dp) ) { items( count = cropperOptionList.size, @@ -93,7 +100,7 @@ fun CropperOptionView( .background(color = borderColor) .padding(selectedBorderWidth) .clip(clipShape) - .background(MaterialTheme.colorScheme.background) + .background(ToolBarBackgroundColor) .padding(4.dp) .clickable { onClick(cropperOption) @@ -111,8 +118,9 @@ fun CropperOptionView( if (cropperOption.aspectRatioX == -1f) { Image( modifier = Modifier.fillMaxSize(), - imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.baseline_crop_free_24), - contentDescription = null, + imageVector = Icons.Default.CropFree, +// imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.baseline_crop_free_24), + contentDescription = "Free Aspect Ratio", colorFilter = ColorFilter.tint( color = MaterialTheme.colorScheme.onBackground ) @@ -120,8 +128,9 @@ fun CropperOptionView( } else if (cropperOption.aspectRatioX == -2f) { Image( modifier = Modifier.fillMaxSize(), - imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.ic_crop), - contentDescription = null, + imageVector = Icons.Default.Crop, +// imageVector = ImageVector.vectorResource(id = com.abizer_r.quickedit.R.drawable.ic_crop), + contentDescription = "Custom Aspect Ratio", colorFilter = ColorFilter.tint( color = MaterialTheme.colorScheme.onBackground ) @@ -155,9 +164,9 @@ fun CropperOptionView( } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview @Composable -fun Selected_EffectPreviewItem() { +private fun Selected_EffectPreviewItem() { QuickEditTheme { CropperOptionView( modifier = Modifier.padding(8.dp), @@ -172,9 +181,9 @@ fun Selected_EffectPreviewItem() { } } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview @Composable -fun Unselected_EffectPreviewItem() { +private fun Unselected_EffectPreviewItem() { QuickEditTheme { CropperOptionView( modifier = Modifier.padding(8.dp), @@ -190,13 +199,12 @@ fun Unselected_EffectPreviewItem() { } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview @Composable -fun Preview_EffectsPreviewList() { +private fun Preview_EffectsPreviewList() { QuickEditTheme { CropperOptionsFullWidth( - modifier = Modifier - .background(ToolBarBackgroundColor) + modifier = Modifier.background(ToolBarBackgroundColor) .padding(vertical = 12.dp), cropperOptionList = CropModeUtils.getCropperOptionsList(), selectedIndex = 0, diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOption.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/model/CropperOption.kt similarity index 67% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOption.kt rename to quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/model/CropperOption.kt index 09758bb..3d358ff 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/cropperOptions/CropperOption.kt +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/model/CropperOption.kt @@ -1,6 +1,5 @@ -package com.abizer_r.quickedit.ui.cropMode.cropperOptions +package io.github.abizerr.quickedit.tool.crop.model -import android.graphics.Bitmap import java.util.UUID data class CropperOption( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/utils/CropModeUtils.kt similarity index 90% rename from quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt rename to quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/utils/CropModeUtils.kt index edea4bb..7fba93f 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/cropMode/CropModeUtils.kt +++ b/quickedit-tool-crop/src/main/java/io/github/abizerr/quickedit/tool/crop/utils/CropModeUtils.kt @@ -1,6 +1,6 @@ -package com.abizer_r.quickedit.utils.editorScreen +package io.github.abizerr.quickedit.tool.crop.utils -import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOption +import io.github.abizerr.quickedit.tool.crop.model.CropperOption object CropModeUtils { diff --git a/quickedit-tool-crop/src/main/res/values/strings.xml b/quickedit-tool-crop/src/main/res/values/strings.xml new file mode 100644 index 0000000..9467dbf --- /dev/null +++ b/quickedit-tool-crop/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + + Enter Aspect Ratio + X + Y + Select + Not a valid number + Fields cannot be empty + Ratio is lesser than minimum allowed + Ratio is greater than maximum allowed + \ No newline at end of file diff --git a/quickedit-tool-crop/src/test/java/io/github/abizerr/quickedit/tool/crop/ExampleUnitTest.kt b/quickedit-tool-crop/src/test/java/io/github/abizerr/quickedit/tool/crop/ExampleUnitTest.kt new file mode 100644 index 0000000..290e05b --- /dev/null +++ b/quickedit-tool-crop/src/test/java/io/github/abizerr/quickedit/tool/crop/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.tool.crop + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/.gitignore b/quickedit-tool-draw/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-tool-draw/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-tool-draw/build.gradle.kts b/quickedit-tool-draw/build.gradle.kts new file mode 100644 index 0000000..c643bdf --- /dev/null +++ b/quickedit-tool-draw/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.github.abizerr.quickedit.tool.draw" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(project(":quickedit-ui")) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui.tooling.preview) + debugImplementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.material.icons.extended) + + implementation(libs.colorpicker) + implementation(libs.compose.screenshot) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/quickedit-tool-draw/consumer-rules.pro b/quickedit-tool-draw/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-tool-draw/proguard-rules.pro b/quickedit-tool-draw/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-tool-draw/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-tool-draw/src/androidTest/java/io/github/abizerr/quickedit/tool/draw/ExampleInstrumentedTest.kt b/quickedit-tool-draw/src/androidTest/java/io/github/abizerr/quickedit/tool/draw/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2ff20ba --- /dev/null +++ b/quickedit-tool-draw/src/androidTest/java/io/github/abizerr/quickedit/tool/draw/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.tool.draw + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.tool.draw.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/AndroidManifest.xml b/quickedit-tool-draw/src/main/AndroidManifest.xml new file mode 100644 index 0000000..582c8f4 --- /dev/null +++ b/quickedit-tool-draw/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolContribution.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolContribution.kt new file mode 100644 index 0000000..7d3d150 --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolContribution.kt @@ -0,0 +1,33 @@ +package io.github.abizerr.quickedit.tool.draw + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Brush +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolContribution +import io.github.abizerr.quickedit.ui.api.ToolContributionWithFactory +import io.github.abizerr.quickedit.ui.api.ToolController +import io.github.abizerr.quickedit.ui.api.ToolFactory + +class DrawToolContribution : ToolContributionWithFactory { + override val id: String = "draw" + override val factory: ToolFactory get() = DrawToolFactory() + override val label: String = "Draw" + override val supportsFullScreen: Boolean get() = true + + @Composable + override fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) { + IconWithLabel( + selected = selected, + imageVector = Icons.Outlined.Brush, + labelText = label, + onClick = onClick + ) + } + + @Composable + override fun Panel(state: QuickEditState, controller: ToolController) { + Text("Draw tool placeholder") + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolFactory.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolFactory.kt new file mode 100644 index 0000000..fe6f00f --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/DrawToolFactory.kt @@ -0,0 +1,10 @@ +package io.github.abizerr.quickedit.tool.draw + +import io.github.abizerr.quickedit.tool.draw.session.DrawToolSession +import io.github.abizerr.quickedit.ui.api.ToolFactory +import io.github.abizerr.quickedit.ui.api.ToolParams +import io.github.abizerr.quickedit.ui.api.ToolSession + +class DrawToolFactory: ToolFactory { + override fun create(params: ToolParams): ToolSession = DrawToolSession() +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/DrawToolItem.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/DrawToolItem.kt new file mode 100644 index 0000000..58bd2ac --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/DrawToolItem.kt @@ -0,0 +1,131 @@ +package io.github.abizerr.quickedit.tool.draw.models + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.ShapeType +import io.github.abizerr.quickedit.tool.draw.models.shapes.BaseShape +import io.github.abizerr.quickedit.tool.draw.models.shapes.BrushShape +import io.github.abizerr.quickedit.tool.draw.models.shapes.LineShape +import io.github.abizerr.quickedit.tool.draw.models.shapes.OvalShape +import io.github.abizerr.quickedit.tool.draw.models.shapes.RectangleShape + +sealed class DrawToolItem { + object NONE: DrawToolItem() + object ColorItem : DrawToolItem() + object PanItem : DrawToolItem() + class BrushTool(var width: Float, var opacity: Float) : DrawToolItem() + class ShapeTool(var width: Float, var opacity: Float, var shapeType: ShapeType) : + DrawToolItem() + class EraserTool(var width: Float) : DrawToolItem() +} + +fun DrawToolItem.getShape( + selectedColor: Color, + scale: Float = 1f, +): BaseShape? { + return when (val toolbarItem = this) { + is DrawToolItem.BrushTool -> { + BrushShape( + shapeSpec = ShapeSpec.Brush( + argb = selectedColor.toArgb(), + widthPx = toolbarItem.width / scale, + alpha = toolbarItem.opacity / 100f + ) + ) + } + + is DrawToolItem.EraserTool -> { + BrushShape( + shapeSpec = ShapeSpec.Brush( + argb = selectedColor.toArgb(), + widthPx = toolbarItem.width / scale, + alpha = 1f, + isEraser = true + ) + ) + } + + is DrawToolItem.ShapeTool -> when (toolbarItem.shapeType) { + ShapeType.LINE -> LineShape( + shapeSpec = ShapeSpec.Shape( + shapeType = ShapeType.LINE, + argb = selectedColor.toArgb(), + widthPx = toolbarItem.width / scale, + alpha = toolbarItem.opacity / 100f + ) + ) + + ShapeType.OVAL -> OvalShape( + shapeSpec = ShapeSpec.Shape( + shapeType = ShapeType.OVAL, + argb = selectedColor.toArgb(), + widthPx = toolbarItem.width / scale, + alpha = toolbarItem.opacity / 100f + ) + ) + + ShapeType.RECTANGLE -> RectangleShape( + shapeSpec = ShapeSpec.Shape( + shapeType = ShapeType.RECTANGLE, + argb = selectedColor.toArgb(), + widthPx = toolbarItem.width / scale, + alpha = toolbarItem.opacity / 100f + ) + ) + } + + else -> null + } +} + +fun DrawToolItem.getWidthOrNull(): Float? { + return when (this) { + is DrawToolItem.BrushTool -> this.width + is DrawToolItem.EraserTool -> this.width + is DrawToolItem.ShapeTool -> this.width + else -> null + } +} + +fun DrawToolItem.setWidthIfPossible(mWidth: Float): DrawToolItem { + when (this) { + is DrawToolItem.BrushTool -> this.width = mWidth + is DrawToolItem.EraserTool -> this.width = mWidth + is DrawToolItem.ShapeTool -> this.width = mWidth + else -> {} + } + return this +} + +fun DrawToolItem.getOpacityOrNull(): Float? { + return when (this) { + is DrawToolItem.BrushTool -> this.opacity + is DrawToolItem.ShapeTool -> this.opacity + else -> null + } +} + +fun DrawToolItem.setOpacityIfPossible(mOpacity: Float): DrawToolItem { + when (this) { + is DrawToolItem.BrushTool -> this.opacity = mOpacity + is DrawToolItem.ShapeTool -> this.opacity = mOpacity + else -> {} + } + return this +} + +fun DrawToolItem.getShapeTypeOrNull(): ShapeType? { + return when (this) { + is DrawToolItem.ShapeTool -> this.shapeType + else -> null + } +} + +fun DrawToolItem.setShapeTypeIfPossible(mShapeType: ShapeType): DrawToolItem { + when (this) { + is DrawToolItem.ShapeTool -> this.shapeType = mShapeType + else -> {} + } + return this +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/PathDetails.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/PathDetails.kt new file mode 100644 index 0000000..f183cd8 --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/PathDetails.kt @@ -0,0 +1,7 @@ +package io.github.abizerr.quickedit.tool.draw.models + +import io.github.abizerr.quickedit.tool.draw.models.shapes.BaseShape + +data class PathDetails( + val drawingShape: BaseShape, +) \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/AbstractShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/AbstractShape.kt new file mode 100644 index 0000000..1f5c4ca --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/AbstractShape.kt @@ -0,0 +1,13 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import androidx.compose.ui.graphics.Color +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.tool.draw.util.DrawingConstants + +abstract class AbstractShape: BaseShape { + abstract var shapeSpec: ShapeSpec +// var mColor: Color = Color.White +// var mWidth: Float = DrawingConstants.DEFAULT_STROKE_WIDTH +// var mAlpha: Float = DrawingConstants.DEFAULT_STROKE_ALPHA + +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BaseShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BaseShape.kt new file mode 100644 index 0000000..efbb47f --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BaseShape.kt @@ -0,0 +1,10 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import android.graphics.Canvas + +interface BaseShape { + fun drawOnAndroidCanvas(canvas: Canvas) + fun initShape(startX: Float, startY: Float) + fun moveShape(endX: Float, endY: Float) + fun shouldDraw(): Boolean +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BrushShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BrushShape.kt new file mode 100644 index 0000000..ce49c99 --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/BrushShape.kt @@ -0,0 +1,31 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import android.graphics.Canvas +import android.graphics.Paint +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec +import io.github.abizerr.quickedit.engine.util.applySpec +import io.github.abizerr.quickedit.engine.util.drawOn + +class BrushShape( + private val shapeSpec: ShapeSpec.Brush +): BaseShape { + + override fun drawOnAndroidCanvas(canvas: Canvas) { + val paintSpec = shapeSpec.toPaintSpec() + val paint = Paint().applySpec(paintSpec) + shapeSpec.drawOn(canvas, paint) + } + + override fun initShape(startX: Float, startY: Float) { + shapeSpec.points.add(Pair(startX, startY)) + } + + override fun moveShape(endX: Float, endY: Float) { + shapeSpec.points.add(Pair(endX, endY)) + } + + override fun shouldDraw(): Boolean { + return true + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/LineShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/LineShape.kt new file mode 100644 index 0000000..1726924 --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/LineShape.kt @@ -0,0 +1,32 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import android.graphics.Canvas +import android.graphics.Paint +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec +import io.github.abizerr.quickedit.engine.util.applySpec +import io.github.abizerr.quickedit.engine.util.drawOn + +class LineShape( + private val shapeSpec: ShapeSpec.Shape +) : BaseShape { + + override fun drawOnAndroidCanvas(canvas: Canvas) { + val paintSpec = shapeSpec.toPaintSpec() + val paint = Paint().applySpec(paintSpec) + shapeSpec.drawOn(canvas, paint) + } + + override fun initShape(startX: Float, startY: Float) { + shapeSpec.startOffset = Pair(startX, startY) + } + + override fun moveShape(endX: Float, endY: Float) { + shapeSpec.endOffset = Pair(endX, endY) + } + + override fun shouldDraw(): Boolean { + return shapeSpec.startOffset != ShapeSpec.defaultOffset + && shapeSpec.endOffset != ShapeSpec.defaultOffset + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/OvalShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/OvalShape.kt new file mode 100644 index 0000000..5568a2f --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/OvalShape.kt @@ -0,0 +1,32 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import android.graphics.Canvas +import android.graphics.Paint +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec +import io.github.abizerr.quickedit.engine.util.applySpec +import io.github.abizerr.quickedit.engine.util.drawOn + +class OvalShape( + private val shapeSpec: ShapeSpec.Shape +) : BaseShape { + + override fun drawOnAndroidCanvas(canvas: Canvas) { + val paintSpec = shapeSpec.toPaintSpec() + val paint = Paint().applySpec(paintSpec) + shapeSpec.drawOn(canvas, paint) + } + + override fun initShape(startX: Float, startY: Float) { + shapeSpec.startOffset = Pair(startX, startY) + } + + override fun moveShape(endX: Float, endY: Float) { + shapeSpec.endOffset = Pair(endX, endY) + } + + override fun shouldDraw(): Boolean { + return shapeSpec.startOffset != ShapeSpec.defaultOffset + && shapeSpec.endOffset != ShapeSpec.defaultOffset + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/RectangleShape.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/RectangleShape.kt new file mode 100644 index 0000000..498c692 --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/models/shapes/RectangleShape.kt @@ -0,0 +1,33 @@ +package io.github.abizerr.quickedit.tool.draw.models.shapes + +import android.graphics.Canvas +import android.graphics.Paint +import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec +import io.github.abizerr.quickedit.engine.drawspec.toPaintSpec +import io.github.abizerr.quickedit.engine.util.applySpec +import io.github.abizerr.quickedit.engine.util.drawOn + +class RectangleShape( + private val shapeSpec: ShapeSpec.Shape +): BaseShape { + + + override fun drawOnAndroidCanvas(canvas: Canvas) { + val paintSpec = shapeSpec.toPaintSpec() + val paint = Paint().applySpec(paintSpec) + shapeSpec.drawOn(canvas, paint) + } + + override fun initShape(startX: Float, startY: Float) { + shapeSpec.startOffset = Pair(startX, startY) + } + + override fun moveShape(endX: Float, endY: Float) { + shapeSpec.endOffset = Pair(endX, endY) + } + + override fun shouldDraw(): Boolean { + return shapeSpec.startOffset != ShapeSpec.defaultOffset + && shapeSpec.endOffset != ShapeSpec.defaultOffset + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/session/DrawToolSession.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/session/DrawToolSession.kt new file mode 100644 index 0000000..fa637fd --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/session/DrawToolSession.kt @@ -0,0 +1,51 @@ +package io.github.abizerr.quickedit.tool.draw.session + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.github.abizerr.quickedit.ui.api.ToolController +import io.github.abizerr.quickedit.ui.api.ToolHost +import io.github.abizerr.quickedit.ui.api.ToolSession + +internal class DrawToolSession : ToolSession { + override val id: String get() = "draw" + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun Ui(host: ToolHost, controller: ToolController, onExit: () -> Unit) { + // Ask shell for exclusive gestures while this full-screen tool is active + + // Placeholder + Surface(modifier = Modifier.Companion.fillMaxSize()) { + Column(Modifier.Companion.fillMaxSize()) { + TopAppBar( + title = { Text("Draw (stub)") }, + actions = { + TextButton(onClick = onExit) { Text("Done") } + } + ) + Box( + modifier = Modifier.Companion + .fillMaxSize() + .padding(16.dp) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Companion.Center + ) { + Text("Canvas placeholder — wiring test") + } + } + } + } +} \ No newline at end of file diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeEvent.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeEvent.kt new file mode 100644 index 0000000..289e0ad --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeEvent.kt @@ -0,0 +1,18 @@ +package io.github.abizerr.quickedit.tool.draw.ui + +import androidx.compose.ui.graphics.Color +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.models.PathDetails +import io.github.abizerr.quickedit.engine.drawspec.ShapeType + +sealed class DrawModeEvent { + data class AddNewPath(val pathDetail: PathDetails): DrawModeEvent() + data class ToggleColorPicker(val selectedColor: Color?): DrawModeEvent() + data class UpdateToolbarExtensionVisibility(val isVisible: Boolean): DrawModeEvent() + object OnUndo: DrawModeEvent() + object OnRedo: DrawModeEvent() + data class OnToolbarItemClicked(val toolbarItem: DrawToolItem): DrawModeEvent() + data class UpdateWidth(val newWidth: Float): DrawModeEvent() + data class UpdateOpacity(val newOpacity: Float): DrawModeEvent() + data class UpdateShapeType(val newShapeType: ShapeType): DrawModeEvent() +} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeState.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeState.kt similarity index 50% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeState.kt rename to quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeState.kt index 057fef4..6b70d5c 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeState.kt +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/DrawModeState.kt @@ -1,14 +1,14 @@ -package com.abizer_r.quickedit.ui.drawMode.stateHandling +package io.github.abizerr.quickedit.tool.draw.ui import androidx.compose.ui.graphics.Color -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.models.PathDetails import java.util.Stack data class DrawModeState( val showColorPicker: Boolean = false, - val selectedColor: Color = Color.White, - val selectedTool: BottomToolbarItem = BottomToolbarItem.NONE, + val selectedColor: Color = Color.Companion.White, + val selectedTool: DrawToolItem = DrawToolItem.NONE, val showBottomToolbarExtension: Boolean = false, val pathDetailStack: Stack = Stack(), val redoStack: Stack = Stack(), diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvas.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/drawingCanvas/DrawingCanvas.kt similarity index 64% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvas.kt rename to quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/drawingCanvas/DrawingCanvas.kt index a8769ae..2b1f552 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvas.kt +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/ui/drawingCanvas/DrawingCanvas.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas +package io.github.abizerr.quickedit.tool.draw.ui.drawingCanvas import android.util.Log import android.view.MotionEvent @@ -14,22 +14,17 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.input.pointer.pointerInteropFilter -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.AbstractShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem -import com.abizer_r.quickedit.utils.drawMode.getShape -import com.abizer_r.quickedit.utils.drawMode.toPx +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.models.PathDetails +import io.github.abizerr.quickedit.tool.draw.models.getShape +import io.github.abizerr.quickedit.tool.draw.models.shapes.BaseShape +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeEvent import java.util.Stack -import kotlin.math.abs @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -37,7 +32,7 @@ fun DrawingCanvas( modifier: Modifier = Modifier, pathDetailStack: Stack, selectedColor: Color, - currentTool: BottomToolbarItem, + currentTool: DrawToolItem, scale: Float, onDrawingEvent: (DrawModeEvent) -> Unit, transformableState: TransformableState, @@ -49,7 +44,7 @@ fun DrawingCanvas( * And when these are changed, the draw phase is called (compose has 3 phases: composition, layout and draw) * SO, Recomposition isn't triggered */ - var currentShape: AbstractShape? = null + var currentShape: BaseShape? = null var drawPhaseTrigger by remember { mutableDoubleStateOf(0.0) } var canvasModifier = modifier @@ -60,16 +55,19 @@ fun DrawingCanvas( translationY = offset.y ) - if (currentTool is BottomToolbarItem.PanItem) { + if (currentTool is DrawToolItem.PanItem) { canvasModifier = canvasModifier .transformable(transformableState) } else { canvasModifier = canvasModifier - .pointerInteropFilter { + .pointerInteropFilter { it -> val adjustedX = it.x / scale val adjustedY = it.y / scale - Log.i("TEST_pan", "Drag: scale = $scale, actualPos = (${it.x}, ${it.y}), adjustedPos = ($adjustedX, $adjustedY)", ) + Log.i( + "TEST_pan", + "Drag: scale = $scale, actualPos = (${it.x}, ${it.y}), adjustedPos = ($adjustedX, $adjustedY)", + ) when (it.action) { MotionEvent.ACTION_DOWN -> { @@ -89,15 +87,15 @@ fun DrawingCanvas( MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { - if (currentShape != null && currentShape!!.shouldDraw()) { - onDrawingEvent( - DrawModeEvent.AddNewPath( - pathDetail = PathDetails( - drawingShape = currentShape!!, + currentShape + ?.takeIf { shape -> shape.shouldDraw() } + ?.let { shape -> + onDrawingEvent( + DrawModeEvent.AddNewPath( + pathDetail = PathDetails(drawingShape = shape) ) ) - ) - } + } } } true @@ -109,15 +107,20 @@ fun DrawingCanvas( modifier = canvasModifier.clipToBounds() ) { pathDetailStack.forEach { pathDetails -> - Log.e("TEST", "DrawingCanvas: drawing from stack. drawingShape = ${pathDetails.drawingShape}", ) - pathDetails.drawingShape.draw( - drawScope = this, + Log.e( + "TEST", + "DrawingCanvas: drawing from stack. drawingShape = ${pathDetails.drawingShape}", ) + drawIntoCanvas { composeCanvas -> + pathDetails.drawingShape.drawOnAndroidCanvas(composeCanvas.nativeCanvas) + } } - Log.e("TEST", "DrawingCanvas: done \n\n\n", ) + Log.e("TEST", "DrawingCanvas: done \n\n\n") if (drawPhaseTrigger > 0) { - currentShape?.draw(drawScope = this) + drawIntoCanvas { composeCanvas -> + currentShape?.drawOnAndroidCanvas(composeCanvas.nativeCanvas) + } } } } diff --git a/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawToolUtil.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawToolUtil.kt new file mode 100644 index 0000000..d0dfe8b --- /dev/null +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawToolUtil.kt @@ -0,0 +1,4 @@ +package io.github.abizerr.quickedit.tool.draw.util + +object DrawToolUtil { +} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawingConstants.kt b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawingConstants.kt similarity index 83% rename from quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawingConstants.kt rename to quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawingConstants.kt index d7c0530..512a094 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawingConstants.kt +++ b/quickedit-tool-draw/src/main/java/io/github/abizerr/quickedit/tool/draw/util/DrawingConstants.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.utils.drawMode +package io.github.abizerr.quickedit.tool.draw.util object DrawingConstants { const val DEFAULT_STROKE_WIDTH = 12f diff --git a/quickedit-tool-draw/src/test/java/io/github/abizerr/quickedit/tool/draw/ExampleUnitTest.kt b/quickedit-tool-draw/src/test/java/io/github/abizerr/quickedit/tool/draw/ExampleUnitTest.kt new file mode 100644 index 0000000..02f7ed7 --- /dev/null +++ b/quickedit-tool-draw/src/test/java/io/github/abizerr/quickedit/tool/draw/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.tool.draw + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit-tool-effects/.gitignore b/quickedit-tool-effects/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-tool-effects/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-tool-effects/build.gradle.kts b/quickedit-tool-effects/build.gradle.kts new file mode 100644 index 0000000..60cc57a --- /dev/null +++ b/quickedit-tool-effects/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.github.abizerr.quickedit.tool.effects" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(project(":quickedit-ui")) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui.tooling.preview) + debugImplementation(libs.androidx.compose.ui.tooling) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/quickedit-tool-effects/consumer-rules.pro b/quickedit-tool-effects/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-tool-effects/proguard-rules.pro b/quickedit-tool-effects/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-tool-effects/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-tool-effects/src/androidTest/java/io/github/abizerr/quickedit/tool/effects/ExampleInstrumentedTest.kt b/quickedit-tool-effects/src/androidTest/java/io/github/abizerr/quickedit/tool/effects/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..41d61cf --- /dev/null +++ b/quickedit-tool-effects/src/androidTest/java/io/github/abizerr/quickedit/tool/effects/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.tool.effects + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.tool.effects.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-tool-effects/src/main/AndroidManifest.xml b/quickedit-tool-effects/src/main/AndroidManifest.xml new file mode 100644 index 0000000..caa0bea --- /dev/null +++ b/quickedit-tool-effects/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit-tool-effects/src/main/java/io/github/abizerr/quickedit/tool/effects/EffectsContribution.kt b/quickedit-tool-effects/src/main/java/io/github/abizerr/quickedit/tool/effects/EffectsContribution.kt new file mode 100644 index 0000000..bb55bcd --- /dev/null +++ b/quickedit-tool-effects/src/main/java/io/github/abizerr/quickedit/tool/effects/EffectsContribution.kt @@ -0,0 +1,29 @@ +package io.github.abizerr.quickedit.tool.effects + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolContribution +import io.github.abizerr.quickedit.ui.api.ToolController + +class EffectsContribution : ToolContribution { + override val id: String = "effects" + override val label: String = "Effects" + + @Composable + override fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) { + IconWithLabel( + selected = selected, + imageVector = ImageVector.vectorResource(id = R.drawable.ic_effects), + labelText = label, + onClick = onClick + ) + } + + @Composable + override fun Panel(state: QuickEditState, controller: ToolController) { + Text("Effects tool placeholder") + } +} \ No newline at end of file diff --git a/quickedit-tool-effects/src/main/res/drawable/ic_effects.xml b/quickedit-tool-effects/src/main/res/drawable/ic_effects.xml new file mode 100644 index 0000000..131ae56 --- /dev/null +++ b/quickedit-tool-effects/src/main/res/drawable/ic_effects.xml @@ -0,0 +1,35 @@ + + + + + + + \ No newline at end of file diff --git a/quickedit-tool-effects/src/test/java/io/github/abizerr/quickedit/tool/effects/ExampleUnitTest.kt b/quickedit-tool-effects/src/test/java/io/github/abizerr/quickedit/tool/effects/ExampleUnitTest.kt new file mode 100644 index 0000000..6cbf705 --- /dev/null +++ b/quickedit-tool-effects/src/test/java/io/github/abizerr/quickedit/tool/effects/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.tool.effects + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit-tool-text/.gitignore b/quickedit-tool-text/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-tool-text/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-tool-text/build.gradle.kts b/quickedit-tool-text/build.gradle.kts new file mode 100644 index 0000000..73b13b3 --- /dev/null +++ b/quickedit-tool-text/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.github.abizerr.quickedit.tool.text" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(project(":quickedit-ui")) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui.tooling.preview) + debugImplementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.material.icons.extended) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/quickedit-tool-text/consumer-rules.pro b/quickedit-tool-text/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-tool-text/proguard-rules.pro b/quickedit-tool-text/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-tool-text/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-tool-text/src/androidTest/java/io/github/abizerr/quickedit/tool/text/ExampleInstrumentedTest.kt b/quickedit-tool-text/src/androidTest/java/io/github/abizerr/quickedit/tool/text/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..93b27fe --- /dev/null +++ b/quickedit-tool-text/src/androidTest/java/io/github/abizerr/quickedit/tool/text/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.tool.text + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.tool.text.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-tool-text/src/main/AndroidManifest.xml b/quickedit-tool-text/src/main/AndroidManifest.xml new file mode 100644 index 0000000..560d129 --- /dev/null +++ b/quickedit-tool-text/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit-tool-text/src/main/java/io/github/abizerr/quickedit/tool/text/TextContribution.kt b/quickedit-tool-text/src/main/java/io/github/abizerr/quickedit/tool/text/TextContribution.kt new file mode 100644 index 0000000..9e2e946 --- /dev/null +++ b/quickedit-tool-text/src/main/java/io/github/abizerr/quickedit/tool/text/TextContribution.kt @@ -0,0 +1,29 @@ +package io.github.abizerr.quickedit.tool.text + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.TextFields +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolContribution +import io.github.abizerr.quickedit.ui.api.ToolController + +class TextContribution : ToolContribution { + override val id: String = "text" + override val label: String = "Text" + + @Composable + override fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) { + IconWithLabel( + selected = selected, + imageVector = Icons.Default.TextFields, + labelText = label, + onClick = onClick + ) + } + + @Composable + override fun Panel(state: QuickEditState, controller: ToolController) { + Text("Text tool placeholder") + } +} \ No newline at end of file diff --git a/quickedit-tool-text/src/test/java/io/github/abizerr/quickedit/tool/text/ExampleUnitTest.kt b/quickedit-tool-text/src/test/java/io/github/abizerr/quickedit/tool/text/ExampleUnitTest.kt new file mode 100644 index 0000000..016ac7e --- /dev/null +++ b/quickedit-tool-text/src/test/java/io/github/abizerr/quickedit/tool/text/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.tool.text + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit-ui/.gitignore b/quickedit-ui/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/quickedit-ui/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickedit-ui/build.gradle.kts b/quickedit-ui/build.gradle.kts new file mode 100644 index 0000000..acd1630 --- /dev/null +++ b/quickedit-ui/build.gradle.kts @@ -0,0 +1,61 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.github.abizerr.quickedit.ui" + compileSdk = 35 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + api(project(":quickedit-core-engine")) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.animation) // for AnimatedContent / transitions + debugImplementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.material.icons.extended) + + implementation(libs.kotlinx.coroutines.android) + + implementation(libs.androidx.constraintlayout.compose) + +// implementation(libs.androidx.core.ktx) +// implementation(libs.androidx.appcompat) +// implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/quickedit-ui/consumer-rules.pro b/quickedit-ui/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/proguard-rules.pro b/quickedit-ui/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/quickedit-ui/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/quickedit-ui/src/androidTest/java/io/github/abizerr/quickedit/ui/ExampleInstrumentedTest.kt b/quickedit-ui/src/androidTest/java/io/github/abizerr/quickedit/ui/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..10c9835 --- /dev/null +++ b/quickedit-ui/src/androidTest/java/io/github/abizerr/quickedit/ui/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.abizerr.quickedit.ui + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.abizerr.quickedit.ui.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/quickedit-ui/src/main/AndroidManifest.xml b/quickedit-ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7791bb8 --- /dev/null +++ b/quickedit-ui/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/QuickEditEditor.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/QuickEditEditor.kt new file mode 100644 index 0000000..6f0efdc --- /dev/null +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/QuickEditEditor.kt @@ -0,0 +1,399 @@ +package io.github.abizerr.quickedit.ui.api + +import android.content.ContentResolver +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Redo +import androidx.compose.material.icons.automirrored.filled.Undo +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.filled.SaveAlt +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Undo +import androidx.compose.material.icons.rounded.Share +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import io.github.abizerr.quickedit.engine.api.EditEngine +import io.github.abizerr.quickedit.engine.api.EditImage +import io.github.abizerr.quickedit.engine.api.EditOp +import io.github.abizerr.quickedit.engine.api.EditSnapshot +import io.github.abizerr.quickedit.engine.api.EditedImage +import io.github.abizerr.quickedit.engine.api.SaveFormat +import io.github.abizerr.quickedit.engine.api.Size +import io.github.abizerr.quickedit.engine.impl.DefaultEditEngine +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.TOOLBAR_HEIGHT_MEDIUM +import io.github.abizerr.quickedit.ui.common.TOOLBAR_HEIGHT_SMALL +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.PreviewUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils.TOOLBAR_COLLAPSE_ANIM_DURATION_FAST +import io.github.abizerr.quickedit.ui.utils.getDummyBitmap +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +data class QuickEditConfig( + val tools: List = emptyList(), + val maxUndo: Int = 20, + val defaultFormat: SaveFormat = SaveFormat.Jpeg(90) +) + +private enum class UiMode { Editor, FullScreenTool } + + +private val topToolbarHeight = TOOLBAR_HEIGHT_SMALL +private val bottomToolbarHeight = TOOLBAR_HEIGHT_MEDIUM + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun QuickEditEditor( + image: EditImage?, + config: QuickEditConfig = QuickEditConfig(), + engine: EditEngine = rememberEngine(config.maxUndo), + onSave: (Result) -> Unit = {} +) { + + var snapshot by remember { mutableStateOf(null) } + var viewport by remember { mutableStateOf(IntSize.Zero) } + var preview by remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() + + var uiMode by remember { mutableStateOf(UiMode.Editor) } + var editorToolbarsVisible by remember { mutableStateOf(true) } + + // Start/replace session when image changes + LaunchedEffect(image, engine) { + snapshot = image?.let { engine.newSession(it) } + } + + // Render when snapshot, viewport or uiMode changes + LaunchedEffect(snapshot, viewport, uiMode) { + if (uiMode != UiMode.Editor) { + preview = null + return@LaunchedEffect // skip preview rendering while a tool is active + } + if (viewport.width <= 0 || viewport.height <= 0) return@LaunchedEffect + val renderResult = engine.render( + snapshot = snapshot ?: return@LaunchedEffect, + size = Size(viewport.width, viewport.height) + ) + preview = renderResult.preview + } + + val state = snapshot?.let { QuickEditState(it) } + + // Controller wires to the engine's apply/undo/redo + val controller = remember(engine, snapshot) { + object : ToolController { + override fun emit(op: EditOp) { + scope.launch { snapshot = engine.apply(op) } + } + + override fun undo() { + scope.launch { snapshot = engine.apply(EditOp.Undo) } + } + + override fun redo() { + scope.launch { snapshot = engine.apply(EditOp.Redo) } + } + + } + } + + var selectedToolId: String? by remember(config.tools) { + mutableStateOf(null) + } + val selectedTool: ToolContribution? = remember(selectedToolId, config.tools) { + config.tools.firstOrNull { it.id == selectedToolId } + } + + suspend fun goToTool(toolId: String) { + // 1) hide toolbars + editorToolbarsVisible = false + delay(TOOLBAR_COLLAPSE_ANIM_DURATION_FAST.toLong()) + // 2) switch to tool + selectedToolId = toolId + uiMode = UiMode.FullScreenTool + } + + suspend fun exitTool() { + // 1) exit tool + uiMode = UiMode.Editor + // 2) show toolbars + editorToolbarsVisible = true + selectedToolId = null + } + + fun saveSnapshot() { + snapshot?.let { + scope.launch { + onSave(engine.save(snapshot!!, config.defaultFormat)) + } + } + } + + Box(Modifier.fillMaxSize()) { + + TopToolbar( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth(), + visible = editorToolbarsVisible, + height = topToolbarHeight, + engine = engine, + controller = controller, + saveEnabled = snapshot != null, + onSave = { saveSnapshot() } + ) + + BottomToolbar( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + visible = editorToolbarsVisible, + height = bottomToolbarHeight, + selectedToolId = selectedToolId, + tools = config.tools, + onToolClicked = { toolId -> + scope.launch { goToTool(toolId) } + } + ) + + Box( + Modifier + .fillMaxSize() + .padding(top = topToolbarHeight, bottom = bottomToolbarHeight) + .onSizeChanged { viewport = it }, + contentAlignment = Alignment.Center + ) { + // Don’t draw the editor preview when a tool is active + if (uiMode == UiMode.Editor) { + if (preview != null) { + Image(bitmap = preview!!.asImageBitmap(), contentDescription = "Preview") + } else { + Text("Preparing preview…") + } + } + } + + // --- Full-screen tool layer (tool owns its own bars & content) --- + if (uiMode == UiMode.FullScreenTool && selectedTool != null && state != null) { + selectedTool.FullScreenTool( + state = state, + controller = controller, + onExit = { scope.launch { exitTool() } } + ) + } + } +} + +@Composable +private fun TopToolbar( + modifier: Modifier = Modifier, + visible: Boolean, + height: Dp, + engine: EditEngine, + controller: ToolController, + saveEnabled: Boolean, + onSave: () -> Unit +) { + AnimatedToolbarContainer( + toolbarVisible = visible, + modifier = modifier + ) { + Surface(tonalElevation = 2.dp) { + Row( + modifier = modifier + .fillMaxWidth() + .height(height) + .background(ToolBarBackgroundColor), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + + // TODO (revamp): handle cancel button + IconButton( + onClick = { /* onCancel() */ }, + enabled = false /* cancelEnabled */ + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.Default.Close, + contentDescription = "Cancel", + ) + } + + Row { + IconButton( + onClick = { controller.undo() }, + enabled = engine.history.canUndo + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.AutoMirrored.Default.Undo, + contentDescription = "Undo", + ) + } + + IconButton( + onClick = { controller.redo() }, + enabled = engine.history.canRedo + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.AutoMirrored.Default.Redo, + contentDescription = "Redo", + ) + } + } + + + IconButton( + onClick = { onSave() }, + enabled = saveEnabled + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = Icons.Default.Save, + contentDescription = "Save", + ) + } + + // TODO (revamp): Implement share button (add a shareEnabled boolean in config) +// IconButton( +// onClick = { /* onShare() */ }, +// enabled = true +// ) { +// Icon( +// modifier = Modifier.size(28.dp), +// imageVector = Icons.Default.Share, +// contentDescription = "Share", +// ) +// } + } + } + } +} + +@Composable +private fun BottomToolbar( + modifier: Modifier = Modifier, + visible: Boolean, + height: Dp, + selectedToolId: String?, + tools: List, + onToolClicked: (toolId: String) -> Unit +) { + AnimatedToolbarContainer( + toolbarVisible = visible, + modifier = modifier + ) { + Surface(tonalElevation = 3.dp) { + Row( + modifier = Modifier + .height(height) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + tools.forEach { tool -> + // Delegate icon rendering to the tool + tool.ToolbarIcon( + selected = tool.id == selectedToolId, + onClick = { onToolClicked(tool.id) } + ) + } + } + } + } +} + + +@Composable +private fun rememberEngine(maxUndo: Int): EditEngine { + val resolver: ContentResolver = LocalContext.current.contentResolver + return remember(resolver, maxUndo) { + DefaultEditEngine( + maxUndo = maxUndo, + resolver = resolver + ) + } +} + +@Preview +@Composable +private fun PreviewTopToolbar() { + QuickEditTheme { + TopToolbar( + visible = true, + height = topToolbarHeight, + engine = DefaultEditEngine(), + controller = PreviewUtils.getDummyToolController(), + saveEnabled = true, + onSave = {} + ) + } +} + +@Preview +@Composable +private fun PreviewBottomToolbar() { + QuickEditTheme { + val tools = PreviewUtils.getDummyTools() + BottomToolbar( + visible = true, + height = bottomToolbarHeight, + selectedToolId = tools[0].id, + tools = tools, + onToolClicked = { toolId -> } + ) + } +} + +@Preview +@Composable +private fun Preview() { + QuickEditTheme { + QuickEditEditor( + image = EditImage.FromBitmap(getDummyBitmap()), + config = QuickEditConfig(tools = PreviewUtils.getDummyTools()), + onSave = {} + ) + } +} \ No newline at end of file diff --git a/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContracts.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContracts.kt new file mode 100644 index 0000000..a64327f --- /dev/null +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContracts.kt @@ -0,0 +1,38 @@ +package io.github.abizerr.quickedit.ui.api + +import androidx.compose.runtime.Composable +import io.github.abizerr.quickedit.engine.api.EditOp +import io.github.abizerr.quickedit.engine.api.EditSnapshot + +interface ToolHost { + /** Latest immutable snapshot produced by the engine. */ + val snapshot: EditSnapshot + + /** Hint the shell to switch to gesture policy (e.g., disable editor pan/zoom */ + fun requestGestureMode(mode: GestureMode) +} + +enum class GestureMode { EditorGestures, ToolExclusive } + +/** A running instance of a tool (state + Composabel UI) */ +interface ToolSession { + /** Stable identifier (e.g., "draw", "crop") */ + val id: String + + /** Tool's full-screen UI; shell passes in host, controller & exit callback */ + @Composable + fun Ui(host: ToolHost, controller: ToolController, onExit: () -> Unit) + + /** Optional: flush pending ops on exit. Kept simple for now. */ + fun onClose(): List = emptyList() +} + +/** Factory keeps the module boundary DI-free. */ +fun interface ToolFactory { + fun create(params: ToolParams): ToolSession +} + +/** Arbitrary inputs supplied by host (e.g., initial brush/ratio) */ +data class ToolParams( + val initial: Map = emptyMap() +) \ No newline at end of file diff --git a/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContribution.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContribution.kt new file mode 100644 index 0000000..8d711ac --- /dev/null +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/api/ToolContribution.kt @@ -0,0 +1,139 @@ +package io.github.abizerr.quickedit.ui.api + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.github.abizerr.quickedit.engine.api.EditOp +import io.github.abizerr.quickedit.engine.api.EditSnapshot + +interface ToolContribution { + val id: String + val label: String get() = id + @Composable + fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) + @Composable + fun Panel(state: QuickEditState, controller: ToolController) + /** + * FUTURE: Add optional lifecycle hooks without breaking: + * fun onAttach() {} + * fun onDetach() {} + * val requiredOps: Set> get() = emptySet() + */ + + /** Optional full-screen tool (own top/bottom bars + content). */ + val supportsFullScreen: Boolean get() = false + @Composable + fun FullScreenTool( + state: QuickEditState, + controller: ToolController, + onExit: () -> Unit + ) { /* default no-op */ } + + @Composable + fun Modifier.selectionModifier(selected: Boolean): Modifier { + return if (selected.not()) { + this + .padding(horizontal = 8.dp, vertical = 4.dp) + } else { + this + .clip(RoundedCornerShape(3.dp)) + .background(MaterialTheme.colorScheme.onBackground) + .padding((0.5).dp) + .clip(RoundedCornerShape(3.dp)) + .background(Color.DarkGray) + .padding(horizontal = 8.dp, vertical = 4.dp) + } + } + + @Composable + fun IconWithLabel( + selected: Boolean, + imageVector: ImageVector, + labelText: String, + onClick: () -> Unit + ) { + val imageSize = 28.dp + val labelTextStyle = MaterialTheme.typography.bodySmall + + Column( + modifier = Modifier + .clickable { onClick() } + .selectionModifier(selected), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + modifier = Modifier.size(imageSize), + contentDescription = labelText, + imageVector = imageVector, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onBackground + ) + ) + Spacer(modifier = Modifier.size(4.dp)) + Text( + style = labelTextStyle, + text = labelText + ) + } + } +} + +interface ToolController { + fun emit(op: EditOp) // placeholder; will be EditOp in future + fun undo() + fun redo() + /** + * FUTURE: add batch ops for atomic changes + * fun emitAll(ops: List) + */ +} + +class QuickEditState internal constructor( + val snapshot: EditSnapshot +) + +/** A contribution that provides a ToolFactory */ +interface ToolContributionWithFactory: ToolContribution { + val factory: ToolFactory + + @Composable + override fun FullScreenTool( + state: QuickEditState, + controller: ToolController, + onExit: () -> Unit + ) { + val host = rememberToolHostFrom(state) + val session = remember { factory.create(ToolParams()) } + session.Ui(host, controller, onExit) + } + +} + +internal fun rememberToolHostFrom(state: QuickEditState): ToolHost = object : ToolHost { + override val snapshot: EditSnapshot + get() = state.snapshot + + override fun requestGestureMode(mode: GestureMode) { + // Shell toggles its state machine to enforce gesture policy while in tool mode + // (editor preview not composed; render paused per design). + } + +} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AnimatedToolbarContainer.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/common/AnimatedToolbarContainer.kt similarity index 80% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AnimatedToolbarContainer.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/common/AnimatedToolbarContainer.kt index d4f77dc..206eedf 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AnimatedToolbarContainer.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/common/AnimatedToolbarContainer.kt @@ -1,17 +1,22 @@ -package com.abizer_r.quickedit.ui.common +package io.github.abizerr.quickedit.ui.common import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstrainedLayoutReference import androidx.constraintlayout.compose.ConstraintLayoutScope import androidx.constraintlayout.compose.Dimension -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils + +val TOOLBAR_HEIGHT_SMALL = 48.dp +val TOOLBAR_HEIGHT_MEDIUM = 64.dp +val TOOLBAR_HEIGHT_LARGE = 88.dp +val TOOLBAR_HEIGHT_XL = 104.dp @Composable fun AnimatedToolbarContainer( @@ -32,7 +37,7 @@ fun AnimatedToolbarContainer( @Composable fun ConstraintLayoutScope.topToolbarModifier( constraintRef: ConstrainedLayoutReference, -) = Modifier.constrainAs(constraintRef) { +) = Modifier.Companion.constrainAs(constraintRef) { top.linkTo(parent.top) width = Dimension.matchParent height = Dimension.wrapContent @@ -41,7 +46,7 @@ fun ConstraintLayoutScope.topToolbarModifier( @Composable fun ConstraintLayoutScope.bottomToolbarModifier( constraintRef: ConstrainedLayoutReference, -) = Modifier.constrainAs(constraintRef) { +) = Modifier.Companion.constrainAs(constraintRef) { bottom.linkTo(parent.bottom) width = Dimension.matchParent height = Dimension.wrapContent diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Color.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Color.kt similarity index 87% rename from quickedit/src/main/java/com/abizer_r/quickedit/theme/Color.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Color.kt index 13d4d9a..851cc9d 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Color.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Color.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.theme +package io.github.abizerr.quickedit.ui.theme import androidx.compose.ui.graphics.Color @@ -17,8 +17,9 @@ val Black_alpha_30 = Color(0x4D000000) val TextInputBackgroundColor = Color(0xB3000000) val ToolBarBackgroundColor = DarkerGray +val White = Color.White val BackgroundColor_Dark = Color(0xFF141414) -val ColorOnBackground_Dark = Color.White +val ColorOnBackground_Dark = White /** * NOTE: While changing Light theme colors, we also need to change static colors, such as "ToolBarBackgroundColor" diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Theme.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Theme.kt similarity index 75% rename from quickedit/src/main/java/com/abizer_r/quickedit/theme/Theme.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Theme.kt index 7a9a824..cd67744 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Theme.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Theme.kt @@ -1,25 +1,28 @@ -package com.abizer_r.quickedit.theme +package io.github.abizerr.quickedit.ui.theme import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat private val DarkColorScheme = darkColorScheme( primary = Purple80, + onPrimary = White, secondary = PurpleGrey80, + onSecondary = White, tertiary = Pink80, + onTertiary = White, background = BackgroundColor_Dark, onBackground = ColorOnBackground_Dark ) @@ -47,10 +50,11 @@ fun QuickEditTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { - val colorScheme = when { - darkTheme -> DarkColorScheme - else -> LightColorScheme - } + val colorScheme = DarkColorScheme +// val colorScheme = when { +// darkTheme -> DarkColorScheme +// else -> LightColorScheme +// } val view = LocalView.current if (!view.isInEditMode) { SideEffect { @@ -69,6 +73,12 @@ fun QuickEditTheme( MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content + content = { + CompositionLocalProvider( + LocalContentColor provides colorScheme.onPrimary, // default text color + ) { + content() + } + } ) } \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Type.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Type.kt similarity index 84% rename from quickedit/src/main/java/com/abizer_r/quickedit/theme/Type.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Type.kt index cb15045..1ac7aad 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/theme/Type.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/theme/Type.kt @@ -1,11 +1,7 @@ -package com.abizer_r.quickedit.theme +package io.github.abizerr.quickedit.ui.theme import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp -import com.abizer_r.quickedit.utils.textMode.FontUtils +import io.github.abizerr.quickedit.ui.utils.font.FontUtils // Set of Material typography styles to start with val Typography = Typography( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/CommonExtensions.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/CommonExtensions.kt similarity index 63% rename from quickedit/src/main/java/com/abizer_r/quickedit/utils/CommonExtensions.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/CommonExtensions.kt index 3141e46..f7a2307 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/CommonExtensions.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/CommonExtensions.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.utils +package io.github.abizerr.quickedit.ui.utils import android.content.Context import android.content.ContextWrapper @@ -8,15 +8,13 @@ import android.provider.Settings import android.widget.Toast import androidx.activity.ComponentActivity import androidx.annotation.StringRes -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope -import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.SharedTransitionScope import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.platform.LocalContext -import com.abizer_r.quickedit.R +import androidx.compose.ui.res.imageResource +import io.github.abizerr.quickedit.ui.R val Any.TAG: String get() { @@ -24,17 +22,17 @@ val Any.TAG: String return if (tag.length <= 23) tag else tag.substring(0, 23) } -@OptIn(ExperimentalSharedTransitionApi::class) -@Composable -fun SharedTransitionPreviewExtension( - content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit -) { - SharedTransitionLayout { - AnimatedVisibility(visible = true) { - content(this) - } - } -} +//@OptIn(ExperimentalSharedTransitionApi::class) +//@Composable +//fun SharedTransitionPreviewExtension( +// content: @Composable SharedTransitionScope.(AnimatedVisibilityScope) -> Unit +//) { +// SharedTransitionLayout { +// AnimatedVisibility(visible = true) { +// content(this) +// } +// } +//} fun Context.toast(message: String) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show() @@ -55,6 +53,11 @@ fun toast(@StringRes stringRes: Int) { LocalContext.current.toast(stringRes) } +fun Context.errorToast(@StringRes resId: Int? = null) { + if (resId == null) defaultErrorToast() + else toast(resId) +} + fun Context.defaultErrorToast() { Toast.makeText(this, this.getString(R.string.something_went_wrong), Toast.LENGTH_SHORT).show() } @@ -73,4 +76,8 @@ fun Context.getOpenAppSettingsIntent(): Intent { } @Composable -fun defaultTextColor() = MaterialTheme.colorScheme.onBackground \ No newline at end of file +fun defaultTextColor() = MaterialTheme.colorScheme.onBackground + + +@Composable +fun getDummyBitmap() = ImageBitmap.imageResource(id = R.drawable.dummy_image).asAndroidBitmap() \ No newline at end of file diff --git a/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/PreviewUtils.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/PreviewUtils.kt new file mode 100644 index 0000000..3e31cab --- /dev/null +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/PreviewUtils.kt @@ -0,0 +1,55 @@ +package io.github.abizerr.quickedit.ui.utils + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.runtime.Composable +import io.github.abizerr.quickedit.engine.api.EditImage +import io.github.abizerr.quickedit.engine.api.EditOp +import io.github.abizerr.quickedit.engine.api.EditSnapshot +import io.github.abizerr.quickedit.ui.api.QuickEditState +import io.github.abizerr.quickedit.ui.api.ToolContribution +import io.github.abizerr.quickedit.ui.api.ToolController + +object PreviewUtils { + + @Composable + fun getDummyEditorState(): QuickEditState { + val snapShot = EditSnapshot(image = EditImage.FromBitmap(getDummyBitmap())) + return QuickEditState(snapShot) + } + + fun getDummyTools(): List { + val tools = arrayListOf() + repeat(4) { i -> + tools.add(getDummyToolContribution(i)) + } + return tools + } + + private fun getDummyToolContribution(i: Int = 0): ToolContribution { + val dummyToolContribution = object : ToolContribution { + override val id: String get() = "dummy$i" + @Composable + override fun Panel(state: QuickEditState, controller: ToolController) { } + @Composable + override fun ToolbarIcon(selected: Boolean, onClick: () -> Unit) { + IconWithLabel( + selected = selected, + imageVector = Icons.Default.Edit, + labelText = "Tool-$i", + onClick = {} + ) + } + } + return dummyToolContribution + } + + fun getDummyToolController(): ToolController { + val dummyController = object : ToolController { + override fun emit(op: EditOp) {} + override fun undo() {} + override fun redo() {} + } + return dummyController + } +} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/anim/AnimUtils.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/anim/AnimUtils.kt similarity index 88% rename from quickedit/src/main/java/com/abizer_r/quickedit/utils/other/anim/AnimUtils.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/anim/AnimUtils.kt index 0b160d3..08111d5 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/other/anim/AnimUtils.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/anim/AnimUtils.kt @@ -1,26 +1,18 @@ -package com.abizer_r.quickedit.utils.other.anim +package io.github.abizerr.quickedit.ui.utils.anim -import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.animation.core.FastOutLinearInEasing import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkOut import androidx.compose.animation.slideIn -import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOut -import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith -import androidx.compose.runtime.Composable import androidx.compose.ui.unit.IntOffset -import androidx.navigation.NavBackStackEntry object AnimUtils { diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontItem.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontItem.kt similarity index 60% rename from quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontItem.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontItem.kt index 9652638..318e253 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontItem.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontItem.kt @@ -1,4 +1,4 @@ -package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions +package io.github.abizerr.quickedit.ui.utils.font import androidx.compose.ui.text.font.FontFamily diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/FontUtils.kt b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontUtils.kt similarity index 53% rename from quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/FontUtils.kt rename to quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontUtils.kt index 9cd31fc..d47ace4 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/FontUtils.kt +++ b/quickedit-ui/src/main/java/io/github/abizerr/quickedit/ui/utils/font/FontUtils.kt @@ -1,72 +1,71 @@ -package com.abizer_r.quickedit.utils.textMode +package io.github.abizerr.quickedit.ui.utils.font import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions.FontItem +import io.github.abizerr.quickedit.ui.R object FontUtils { val eduVicwantFontFamily = FontFamily( - Font(R.font.edu_vicwant_regular, FontWeight.Normal), - Font(R.font.edu_vicwant_bold, FontWeight.Bold), + Font(R.font.edu_vicwant_regular, FontWeight.Companion.Normal), + Font(R.font.edu_vicwant_bold, FontWeight.Companion.Bold), ) val greyQoFontFamily = FontFamily( - Font(R.font.grey_qo_regular, FontWeight.Normal), + Font(R.font.grey_qo_regular, FontWeight.Companion.Normal), ) val matemasieFontFamily = FontFamily( - Font(R.font.matemasie_regular, FontWeight.Bold), + Font(R.font.matemasie_regular, FontWeight.Companion.Bold), ) val moderusticFontFamily = FontFamily( - Font(R.font.moderustic_regular, FontWeight.Normal), - Font(R.font.moderustic_bold, FontWeight.Bold), + Font(R.font.moderustic_regular, FontWeight.Companion.Normal), + Font(R.font.moderustic_bold, FontWeight.Companion.Bold), ) val montserratFontFamily = FontFamily( - Font(R.font.montserrat_regular, FontWeight.Normal), - Font(R.font.montserrat_italic, FontWeight.Normal, FontStyle.Italic), - Font(R.font.montserrat_bold, FontWeight.Bold), - Font(R.font.montserrat_bold_italic, FontWeight.Bold, FontStyle.Italic), + Font(R.font.montserrat_regular, FontWeight.Companion.Normal), + Font(R.font.montserrat_italic, FontWeight.Companion.Normal, FontStyle.Companion.Italic), + Font(R.font.montserrat_bold, FontWeight.Companion.Bold), + Font(R.font.montserrat_bold_italic, FontWeight.Companion.Bold, FontStyle.Companion.Italic), ) val newAmsterdamFontFamily = FontFamily( - Font(R.font.new_amsterdam_regular, FontWeight.Bold), + Font(R.font.new_amsterdam_regular, FontWeight.Companion.Bold), ) val oswaldFontFamily = FontFamily( - Font(R.font.oswald_regular, FontWeight.Normal), - Font(R.font.oswald_bold, FontWeight.Bold), + Font(R.font.oswald_regular, FontWeight.Companion.Normal), + Font(R.font.oswald_bold, FontWeight.Companion.Bold), ) val playwrightFontFamily = FontFamily( - Font(R.font.playwrite_regular, FontWeight.Normal), - Font(R.font.playwrite_italic, FontWeight.Normal, FontStyle.Italic), + Font(R.font.playwrite_regular, FontWeight.Companion.Normal), + Font(R.font.playwrite_italic, FontWeight.Companion.Normal, FontStyle.Companion.Italic), ) val poppinsFontFamily = FontFamily( - Font(R.font.poppins_regular, FontWeight.Normal), - Font(R.font.poppins_italic, FontWeight.Normal, FontStyle.Italic), - Font(R.font.poppins_bold, FontWeight.Bold), - Font(R.font.poppins_bold_italic, FontWeight.Bold, FontStyle.Italic), + Font(R.font.poppins_regular, FontWeight.Companion.Normal), + Font(R.font.poppins_italic, FontWeight.Companion.Normal, FontStyle.Companion.Italic), + Font(R.font.poppins_bold, FontWeight.Companion.Bold), + Font(R.font.poppins_bold_italic, FontWeight.Companion.Bold, FontStyle.Companion.Italic), ) val robotoFontFamily = FontFamily( - Font(R.font.roboto_regular, FontWeight.Normal), - Font(R.font.roboto_italic, FontWeight.Normal, FontStyle.Italic), - Font(R.font.roboto_bold, FontWeight.Bold), - Font(R.font.roboto_bold_italic, FontWeight.Bold, FontStyle.Italic), + Font(R.font.roboto_regular, FontWeight.Companion.Normal), + Font(R.font.roboto_italic, FontWeight.Companion.Normal, FontStyle.Companion.Italic), + Font(R.font.roboto_bold, FontWeight.Companion.Bold), + Font(R.font.roboto_bold_italic, FontWeight.Companion.Bold, FontStyle.Companion.Italic), ) val tekoFontFamily = FontFamily( - Font(R.font.teko_regular, FontWeight.Normal), - Font(R.font.teko_bold, FontWeight.Bold), + Font(R.font.teko_regular, FontWeight.Companion.Normal), + Font(R.font.teko_bold, FontWeight.Companion.Bold), ) val DefaultFontFamily = poppinsFontFamily diff --git a/quickedit-ui/src/main/res/drawable/dummy_image.jpg b/quickedit-ui/src/main/res/drawable/dummy_image.jpg new file mode 100644 index 0000000..a1d0ad5 Binary files /dev/null and b/quickedit-ui/src/main/res/drawable/dummy_image.jpg differ diff --git a/quickedit-ui/src/main/res/font/edu_vicwant_bold.ttf b/quickedit-ui/src/main/res/font/edu_vicwant_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit/src/main/res/font/edu_vicwant_regular.ttf b/quickedit-ui/src/main/res/font/edu_vicwant_regular.ttf similarity index 100% rename from quickedit/src/main/res/font/edu_vicwant_regular.ttf rename to quickedit-ui/src/main/res/font/edu_vicwant_regular.ttf diff --git a/quickedit/src/main/res/font/grey_qo_regular.ttf b/quickedit-ui/src/main/res/font/grey_qo_regular.ttf similarity index 100% rename from quickedit/src/main/res/font/grey_qo_regular.ttf rename to quickedit-ui/src/main/res/font/grey_qo_regular.ttf diff --git a/quickedit-ui/src/main/res/font/matemasie_regular.ttf b/quickedit-ui/src/main/res/font/matemasie_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/moderustic_bold.ttf b/quickedit-ui/src/main/res/font/moderustic_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit/src/main/res/font/moderustic_regular.ttf b/quickedit-ui/src/main/res/font/moderustic_regular.ttf similarity index 100% rename from quickedit/src/main/res/font/moderustic_regular.ttf rename to quickedit-ui/src/main/res/font/moderustic_regular.ttf diff --git a/quickedit-ui/src/main/res/font/montserrat_bold.ttf b/quickedit-ui/src/main/res/font/montserrat_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/montserrat_bold_italic.ttf b/quickedit-ui/src/main/res/font/montserrat_bold_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/montserrat_italic.ttf b/quickedit-ui/src/main/res/font/montserrat_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/montserrat_regular.ttf b/quickedit-ui/src/main/res/font/montserrat_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit/src/main/res/font/new_amsterdam_regular.ttf b/quickedit-ui/src/main/res/font/new_amsterdam_regular.ttf similarity index 100% rename from quickedit/src/main/res/font/new_amsterdam_regular.ttf rename to quickedit-ui/src/main/res/font/new_amsterdam_regular.ttf diff --git a/quickedit-ui/src/main/res/font/oswald_bold.ttf b/quickedit-ui/src/main/res/font/oswald_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/oswald_regular.ttf b/quickedit-ui/src/main/res/font/oswald_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/playwrite_italic.ttf b/quickedit-ui/src/main/res/font/playwrite_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/playwrite_regular.ttf b/quickedit-ui/src/main/res/font/playwrite_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit/src/main/res/font/poppins_bold.ttf b/quickedit-ui/src/main/res/font/poppins_bold.ttf similarity index 100% rename from quickedit/src/main/res/font/poppins_bold.ttf rename to quickedit-ui/src/main/res/font/poppins_bold.ttf diff --git a/quickedit-ui/src/main/res/font/poppins_bold_italic.ttf b/quickedit-ui/src/main/res/font/poppins_bold_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/poppins_italic.ttf b/quickedit-ui/src/main/res/font/poppins_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit/src/main/res/font/poppins_regular.ttf b/quickedit-ui/src/main/res/font/poppins_regular.ttf similarity index 100% rename from quickedit/src/main/res/font/poppins_regular.ttf rename to quickedit-ui/src/main/res/font/poppins_regular.ttf diff --git a/quickedit-ui/src/main/res/font/roboto_bold.ttf b/quickedit-ui/src/main/res/font/roboto_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/roboto_bold_italic.ttf b/quickedit-ui/src/main/res/font/roboto_bold_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/roboto_italic.ttf b/quickedit-ui/src/main/res/font/roboto_italic.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/roboto_regular.ttf b/quickedit-ui/src/main/res/font/roboto_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/teko_bold.ttf b/quickedit-ui/src/main/res/font/teko_bold.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/font/teko_regular.ttf b/quickedit-ui/src/main/res/font/teko_regular.ttf new file mode 100644 index 0000000..e69de29 diff --git a/quickedit-ui/src/main/res/values/strings.xml b/quickedit-ui/src/main/res/values/strings.xml new file mode 100644 index 0000000..eeabb56 --- /dev/null +++ b/quickedit-ui/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Something went wrong + \ No newline at end of file diff --git a/quickedit-ui/src/test/java/io/github/abizerr/quickedit/ui/ExampleUnitTest.kt b/quickedit-ui/src/test/java/io/github/abizerr/quickedit/ui/ExampleUnitTest.kt new file mode 100644 index 0000000..67fbf52 --- /dev/null +++ b/quickedit-ui/src/test/java/io/github/abizerr/quickedit/ui/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.abizerr.quickedit.ui + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/quickedit/build.gradle.kts b/quickedit/build.gradle.kts index cb4363e..802eedd 100644 --- a/quickedit/build.gradle.kts +++ b/quickedit/build.gradle.kts @@ -42,6 +42,11 @@ android { } dependencies { + implementation(project(":quickedit-ui")) + implementation(project(":quickedit-tool-draw")) + implementation(project(":quickedit-tool-text")) + implementation(project(":quickedit-tool-crop")) + implementation(project(":quickedit-tool-effects")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AppIconWithName.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AppIconWithName.kt index 9aa30fc..5045780 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AppIconWithName.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/AppIconWithName.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.utils.defaultTextColor @Preview @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/ErrorView.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/ErrorView.kt index a1acea6..66cbbd7 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/ErrorView.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/ErrorView.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.utils.defaultTextColor @Composable fun ErrorView( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/PermissionDeniedView.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/PermissionDeniedView.kt index 5655d00..189934b 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/PermissionDeniedView.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/PermissionDeniedView.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.utils.defaultTextColor import com.abizer_r.quickedit.utils.PermissionUtils import com.abizer_r.quickedit.utils.PermissionUtils.PermissionTypes diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/toolbar/SelectableToolbarItem.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/toolbar/SelectableToolbarItem.kt index b9bd0fb..ff2aa0e 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/toolbar/SelectableToolbarItem.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/common/toolbar/SelectableToolbarItem.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme @Composable fun SelectableToolbarItem( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt index e7f2f15..15c3c77 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/cropMode/CropperScreen.kt @@ -32,22 +32,22 @@ import androidx.constraintlayout.compose.Dimension import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.lifecycleScope import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.utils.defaultErrorToast -import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer -import com.abizer_r.quickedit.ui.common.bottomToolbarModifier -import com.abizer_r.quickedit.ui.common.crop.AspectRatioDialog -import com.abizer_r.quickedit.ui.common.topToolbarModifier -import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOption -import com.abizer_r.quickedit.ui.cropMode.cropperOptions.CropperOptionsFullWidth +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.utils.defaultErrorToast +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.bottomToolbarModifier +import io.github.abizerr.quickedit.tool.crop.AspectRatioDialog +import io.github.abizerr.quickedit.ui.common.topToolbarModifier +import io.github.abizerr.quickedit.tool.crop.model.CropperOption +import io.github.abizerr.quickedit.tool.crop.CropperOptionsFullWidth import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_LARGE import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar -import com.abizer_r.quickedit.utils.editorScreen.CropModeUtils -import com.abizer_r.quickedit.utils.getActivity -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.tool.crop.utils.CropModeUtils +import io.github.abizerr.quickedit.ui.utils.getActivity +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap -import com.abizer_r.quickedit.utils.toast +import io.github.abizerr.quickedit.ui.utils.toast import com.canhub.cropper.CropImageOptions import com.canhub.cropper.CropImageView import com.canhub.cropper.CropImageView.OnCropImageCompleteListener diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeScreen.kt index d616a39..5b93725 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeScreen.kt @@ -1,6 +1,5 @@ package com.abizer_r.quickedit.ui.drawMode -import com.abizer_r.quickedit.ui.drawMode.bottomToolbarExtension.DrawModeToolbarExtension import android.graphics.Bitmap import android.util.Log import androidx.activity.compose.BackHandler @@ -13,13 +12,27 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.rememberTransformableState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircleOutline +import androidx.compose.material.icons.filled.TextFields +import androidx.compose.material.icons.outlined.Brush +import androidx.compose.material.icons.outlined.Category +import androidx.compose.material.icons.outlined.Crop +import androidx.compose.material.icons.outlined.PanTool import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -28,47 +41,58 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.imageResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.zIndex import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import com.abizer_r.quickedit.utils.ImmutableList -import com.abizer_r.quickedit.utils.defaultErrorToast -import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer -import com.abizer_r.quickedit.ui.common.bottomToolbarModifier -import com.abizer_r.quickedit.ui.common.topToolbarModifier -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.BottomToolBarStatic +import com.abizer_r.quickedit.R +import com.abizer_r.quickedit.ui.drawMode.bottomToolbarExtension.DrawModeToolbarExtension +import com.abizer_r.quickedit.ui.drawMode.toptoolbar.DrawModeTopToolBar import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_MEDIUM import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarEvent -import com.abizer_r.quickedit.ui.drawMode.toptoolbar.DrawModeTopToolBar import com.abizer_r.quickedit.utils.AppUtils +import com.abizer_r.quickedit.utils.ImmutableList import com.abizer_r.quickedit.utils.drawMode.DrawModeUtils -import com.abizer_r.quickedit.utils.drawMode.getOpacityOrNull -import com.abizer_r.quickedit.utils.drawMode.getShapeTypeOrNull -import com.abizer_r.quickedit.utils.drawMode.getWidthOrNull import com.abizer_r.quickedit.utils.drawMode.toPx -import com.abizer_r.quickedit.utils.other.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap import com.smarttoolfactory.screenshot.ImageResult import com.smarttoolfactory.screenshot.ScreenshotBox import com.smarttoolfactory.screenshot.rememberScreenshotState +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.models.getOpacityOrNull +import io.github.abizerr.quickedit.tool.draw.models.getShapeTypeOrNull +import io.github.abizerr.quickedit.tool.draw.models.getWidthOrNull +import io.github.abizerr.quickedit.engine.drawspec.ShapeType +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeEvent +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.bottomToolbarModifier +import io.github.abizerr.quickedit.ui.common.topToolbarModifier +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.defaultErrorToast +import io.github.abizerr.quickedit.ui.utils.defaultTextColor import io.mhssn.colorpicker.ColorPickerDialog import io.mhssn.colorpicker.ColorPickerType import kotlinx.coroutines.Dispatchers @@ -95,7 +119,7 @@ fun DrawModeScreen( val backgroundColor = MaterialTheme.colorScheme.background val bottomToolbarItems = remember { - ImmutableList(DrawModeUtils.getDefaultBottomToolbarItemsList()) + ImmutableList(DrawModeUtils.getDefaultDrawToolItemsList()) } val topToolbarHeight = TOOLBAR_HEIGHT_SMALL @@ -139,8 +163,8 @@ fun DrawModeScreen( LaunchedEffect(key1 = Unit) { toolbarVisible = true delay(AnimUtils.TOOLBAR_EXPAND_ANIM_DURATION_FAST.toLong()) - viewModel.onBottomToolbarEvent( - BottomToolbarEvent.OnItemClicked( + viewModel.onEvent( + DrawModeEvent.OnToolbarItemClicked( bottomToolbarItems.items[DrawModeUtils.DEFAULT_SELECTED_INDEX] ) ) @@ -218,8 +242,8 @@ fun DrawModeScreen( val onRedoLambda = remember<() -> Unit> {{ viewModel.onEvent(DrawModeEvent.OnRedo) }} - val onBottomToolbarEventLambda = remember<(BottomToolbarEvent) -> Unit> {{ - viewModel.onBottomToolbarEvent(it) + val onBottomToolbarEventLambda = remember<(DrawModeEvent) -> Unit> {{ + viewModel.onEvent(it) }} ConstraintLayout( @@ -291,7 +315,7 @@ fun DrawModeScreen( showColorPickerIcon = viewModel.showColorPickerIconInToolbar, toolbarHeight = bottomToolbarHeight, selectedColor = state.selectedColor, - selectedItem = state.selectedTool, + selectedItem = state.selectedTool, // comment to support latest changes onEvent = onBottomToolbarEventLambda ) } @@ -332,18 +356,18 @@ fun DrawModeScreen( val emptyLambda = remember<() -> Unit> {{ }} val onWidthChangeLambda = remember<(Float) -> Unit> {{ mWidth -> - viewModel.onBottomToolbarEvent( - BottomToolbarEvent.UpdateWidth(mWidth) + viewModel.onEvent( + DrawModeEvent.UpdateWidth(mWidth) ) }} val onOpacityChangeLambda = remember<(Float) -> Unit> {{ mOpacity -> - viewModel.onBottomToolbarEvent( - BottomToolbarEvent.UpdateOpacity(mOpacity) + viewModel.onEvent( + DrawModeEvent.UpdateOpacity(mOpacity) ) }} val onShapeTypeChangeLambda = remember<(ShapeType) -> Unit> {{ mShapeType -> - viewModel.onBottomToolbarEvent( - BottomToolbarEvent.UpdateShapeType(mShapeType) + viewModel.onEvent( + DrawModeEvent.UpdateShapeType(mShapeType) ) }} @@ -387,4 +411,192 @@ fun DrawModeScreen( ) } +} + +val TOOLBAR_HEIGHT_SMALL = 48.dp +val TOOLBAR_HEIGHT_MEDIUM = 64.dp +val TOOLBAR_HEIGHT_LARGE = 88.dp +val TOOLBAR_HEIGHT_EXTRA_LARGE = 104.dp + +@Composable +private fun BottomToolBarStatic( + modifier: Modifier, + toolbarItems: ImmutableList, + toolbarHeight: Dp = TOOLBAR_HEIGHT_MEDIUM, + selectedItem: DrawToolItem = DrawToolItem.NONE, + showColorPickerIcon: Boolean = true, + selectedColor: Color = Color.White, + onEvent: (DrawModeEvent) -> Unit +) { + + Row( + modifier = modifier + .fillMaxWidth() + .height(toolbarHeight) + .background(ToolBarBackgroundColor), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + toolbarItems.items.forEachIndexed { index, mToolbarItem -> + ToolbarItem( + toolbarItem = mToolbarItem, + selectedColor = selectedColor, + showColorPickerIcon = showColorPickerIcon, + isSelected = mToolbarItem == selectedItem, + onEvent = onEvent + ) + } + } +} + + +@Composable +private fun ToolbarItem( + modifier: Modifier = Modifier, + selectedColor: Color, + showColorPickerIcon: Boolean, + toolbarItem: DrawToolItem, + isSelected: Boolean, + onEvent: (DrawModeEvent) -> Unit +) { + val labelTextStyle = MaterialTheme.typography.bodySmall.copy(color = defaultTextColor()) + + val commonPaddingModifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + + if (toolbarItem is DrawToolItem.ColorItem) { + ColorToolbarItem( + modifier = modifier.then(commonPaddingModifier), + selectedColor = selectedColor, + showColorPickerIcon = showColorPickerIcon, + colorItem = toolbarItem, + labelTextStyle = labelTextStyle, + onEvent = onEvent + ) + return + } + var columnModifier = modifier.clickable { + onEvent(DrawModeEvent.OnToolbarItemClicked(toolbarItem)) + } + if (isSelected) { + columnModifier = columnModifier + .clip(RoundedCornerShape(3.dp)) + .background(MaterialTheme.colorScheme.onBackground) + .padding((0.5).dp) + .clip(RoundedCornerShape(3.dp)) + .background(Color.DarkGray) +// .padding(start = 8.dp, end = 8.dp, bottom = 4.dp) + } + columnModifier = columnModifier.then(commonPaddingModifier) + + + val (imageVector, labelText) = when (toolbarItem) { + + is DrawToolItem.EraserTool -> Pair( + ImageVector.vectorResource(id = R.drawable.ic_eraser), + stringResource(id = R.string.eraser) + ) + + is DrawToolItem.ShapeTool -> Pair( + Icons.Outlined.Category, + stringResource(id = R.string.shape) + ) + + is DrawToolItem.BrushTool -> Pair( + ImageVector.vectorResource(id = R.drawable.ic_stylus_note), + stringResource(id = R.string.brush) + ) + + is DrawToolItem.PanItem -> Pair( + Icons.Outlined.PanTool, + stringResource(id = R.string.zoom) + ) + + // We won't reach here + else -> Pair( + Icons.Default.AddCircleOutline, + "" + ) + } + + + Column( + modifier = columnModifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + + val verticalPaddingBeforeSize = if (labelText.isBlank()) 4.dp else 0.dp + val imageSize = if (labelText.isBlank()) 32.dp else 28.dp + Image( + modifier = Modifier + .padding(vertical = verticalPaddingBeforeSize) + .size(imageSize), + contentDescription = null, + imageVector = imageVector, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onBackground + ) + ) + Spacer(modifier = Modifier.size( + if (labelText.isNotBlank()) 4.dp else 0.dp + )) + + if (labelText.isNotBlank()) { + Text( + style = labelTextStyle, + text = labelText + ) + } + } +} + +@Composable +private fun ColorToolbarItem( + modifier: Modifier = Modifier, + selectedColor: Color, + showColorPickerIcon: Boolean, + colorItem: DrawToolItem.ColorItem, + labelTextStyle: TextStyle, + onEvent: (DrawModeEvent) -> Unit +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + + if (showColorPickerIcon) { + Image( + bitmap = ImageBitmap.imageResource(id = R.drawable.ic_color_picker), + contentDescription = null, + modifier = Modifier + .size(26.dp) + .clickable { + onEvent(DrawModeEvent.OnToolbarItemClicked(colorItem)) + } + ) + } else { + Image( + painter = ColorPainter(selectedColor), + contentDescription = null, + modifier = Modifier + .clip(CircleShape) + .background(color = MaterialTheme.colorScheme.onBackground) + .padding(1.dp) + .clip(CircleShape) + .size(26.dp) + .clickable { + onEvent(DrawModeEvent.OnToolbarItemClicked(colorItem)) + } + ) + } + + + Spacer(modifier = Modifier.size(4.dp)) + + Text( + style = labelTextStyle, + text = stringResource(id = R.string.color) + ) + } } \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeViewModel.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeViewModel.kt index 90158da..eb3a329 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeViewModel.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawModeViewModel.kt @@ -1,19 +1,16 @@ package com.abizer_r.quickedit.ui.drawMode -import android.icu.util.Calendar import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeState -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarEvent -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem -import com.abizer_r.quickedit.utils.drawMode.DrawModeUtils -import com.abizer_r.quickedit.utils.drawMode.setOpacityIfPossible -import com.abizer_r.quickedit.utils.drawMode.setShapeTypeIfPossible -import com.abizer_r.quickedit.utils.drawMode.setWidthIfPossible -import com.abizer_r.quickedit.utils.other.anim.AnimUtils import dagger.hilt.android.lifecycle.HiltViewModel +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.models.setOpacityIfPossible +import io.github.abizerr.quickedit.tool.draw.models.setShapeTypeIfPossible +import io.github.abizerr.quickedit.tool.draw.models.setWidthIfPossible +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeEvent +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeState +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -80,43 +77,37 @@ class DrawModeViewModel @Inject constructor( it.copy(recompositionTrigger = it.recompositionTrigger + 1) } } - } - } - fun onBottomToolbarEvent(event: BottomToolbarEvent) { - when (event) { - is BottomToolbarEvent.OnItemClicked -> { - onBottomToolbarItemClicked(event.toolbarItem) + is DrawModeEvent.OnToolbarItemClicked -> { + handleToolbarItemClicked(event.toolbarItem) } - is BottomToolbarEvent.UpdateOpacity -> { + is DrawModeEvent.UpdateOpacity -> { _state.update { it.copy( selectedTool = it.selectedTool.setOpacityIfPossible(event.newOpacity), recompositionTrigger = it.recompositionTrigger + 1 ) } } - is BottomToolbarEvent.UpdateWidth -> { + is DrawModeEvent.UpdateWidth -> { _state.update { it.copy( selectedTool = it.selectedTool.setWidthIfPossible(event.newWidth), recompositionTrigger = it.recompositionTrigger + 1 ) } } - is BottomToolbarEvent.UpdateShapeType -> { + is DrawModeEvent.UpdateShapeType -> { _state.update { it.copy( selectedTool = it.selectedTool.setShapeTypeIfPossible(event.newShapeType), recompositionTrigger = it.recompositionTrigger + 1 ) } } - - else -> {} } } - private fun onBottomToolbarItemClicked(selectedItem: BottomToolbarItem) = viewModelScope.launch { + private fun handleToolbarItemClicked(selectedItem: DrawToolItem) = viewModelScope.launch { when (selectedItem) { - is BottomToolbarItem.ColorItem -> { + is DrawToolItem.ColorItem -> { _state.update { it.copy(showColorPicker = it.showColorPicker.not()) } @@ -124,7 +115,7 @@ class DrawModeViewModel @Inject constructor( // Clicked on already selected item state.value.selectedTool -> { - if (selectedItem != BottomToolbarItem.PanItem) { + if (selectedItem != DrawToolItem.PanItem) { _state.update { it.copy(showBottomToolbarExtension = it.showBottomToolbarExtension.not()) } @@ -139,7 +130,7 @@ class DrawModeViewModel @Inject constructor( delay(AnimUtils.TOOLBAR_COLLAPSE_ANIM_DURATION.toLong()) } _state.update { it.copy(selectedTool = selectedItem) } - if (selectedItem != BottomToolbarItem.PanItem) { + if (selectedItem != DrawToolItem.PanItem) { // open toolbarExtension for new item _state.update { it.copy(showBottomToolbarExtension = true) } } diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawingCanvasContainer.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawingCanvasContainer.kt index 8af46a7..cc9ce4f 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawingCanvasContainer.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/DrawingCanvasContainer.kt @@ -1,10 +1,8 @@ package com.abizer_r.quickedit.ui.drawMode -import android.util.Log import android.view.View import androidx.compose.foundation.Image import androidx.compose.foundation.gestures.TransformableState -import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize @@ -13,20 +11,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.DrawingCanvas -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeEvent -import com.abizer_r.quickedit.ui.drawMode.stateHandling.DrawModeState -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarEvent import com.abizer_r.quickedit.utils.drawMode.CustomLayerTypeComposable -import com.abizer_r.quickedit.utils.drawMode.toPx import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap -import kotlin.math.abs +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeEvent +import io.github.abizerr.quickedit.tool.draw.ui.DrawModeState +import io.github.abizerr.quickedit.tool.draw.ui.drawingCanvas.DrawingCanvas @Composable fun DrawingCanvasContainer( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/CustomSliderItem.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/CustomSliderItem.kt index 71c5d01..e22483f 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/CustomSliderItem.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/CustomSliderItem.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.utils.defaultTextColor import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/DrawModeToolbarExtension.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/DrawModeToolbarExtension.kt index 1b7eb7c..015c393 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/DrawModeToolbarExtension.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/bottomToolbarExtension/DrawModeToolbarExtension.kt @@ -18,15 +18,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType -import com.abizer_r.quickedit.utils.defaultTextColor -import com.abizer_r.quickedit.utils.drawMode.DrawingConstants +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.engine.drawspec.ShapeType +import io.github.abizerr.quickedit.ui.utils.defaultTextColor +import io.github.abizerr.quickedit.tool.draw.util.DrawingConstants /** * Contains additional option for drawMode tools diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvasState.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvasState.kt deleted file mode 100644 index 556aa8a..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/DrawingCanvasState.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas - -import androidx.compose.ui.graphics.Color -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.DrawingTool -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails -import java.util.Stack - -data class DrawingCanvasState ( - val strokeWidth: Int, - val strokeColor: Color, - val drawingTool: DrawingTool, - val opacity: Int, - val pathDetailStack: Stack, - val redoStack: Stack -) \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/DrawingTool.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/DrawingTool.kt deleted file mode 100644 index 67e0f43..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/DrawingTool.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool - -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType - -/** - * TODO: delete this class (this is not used in EditorScreen implementation) - */ -sealed class DrawingTool { - object Brush : DrawingTool() - - object Eraser : DrawingTool() - - class Shape( - val shapeType: ShapeType - ) : DrawingTool() -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/AbstractShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/AbstractShape.kt deleted file mode 100644 index ffb7cca..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/AbstractShape.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.graphics.Color -import com.abizer_r.quickedit.utils.drawMode.DrawingConstants - -abstract class AbstractShape: BaseShape { - var mColor: Color = Color.White - var mWidth: Float = DrawingConstants.DEFAULT_STROKE_WIDTH - var mAlpha: Float = DrawingConstants.DEFAULT_STROKE_ALPHA - - fun updatePaintValues( - color: Color? = null, - width: Float? = null, - alpha: Float? = null - ) { - color?.let { mColor = it } - width?.let { mWidth = it } - alpha?.let { mAlpha = it } - } -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BaseShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BaseShape.kt deleted file mode 100644 index a6c92e1..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BaseShape.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.graphics.drawscope.DrawScope - -interface BaseShape { - fun draw(drawScope: DrawScope) - fun initShape(startX: Float, startY: Float) - fun moveShape(endX: Float, endY: Float) - fun shouldDraw(): Boolean -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BrushShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BrushShape.kt deleted file mode 100644 index a986003..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/BrushShape.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke - -class BrushShape( - private val isEraser: Boolean = false, - color: Color? = null, - width: Float? = null, - alpha: Float? = null -): AbstractShape() { - - init { - updatePaintValues(color, width, alpha) - } - - private var path = Path() - private var prevOffSet = Offset.Zero - - override fun draw(drawScope: DrawScope) { - drawScope.drawPath( - path = path, - brush = SolidColor(mColor), - style = Stroke( - width = mWidth, - cap = StrokeCap.Round, - join = StrokeJoin.Round - ), - alpha = mAlpha, - blendMode = if (isEraser) BlendMode.Clear else BlendMode.SrcOver - ) - } - - override fun initShape(startX: Float, startY: Float) { - path = Path() - path.moveTo(startX, startY) - prevOffSet = Offset(startX, startY) - } - - override fun moveShape(endX: Float, endY: Float) { - /** - * Following this answer for SO: - * https://stackoverflow.com/a/71090112/23198795 - */ - path.quadraticBezierTo( - x1 = prevOffSet.x, - y1 = prevOffSet.y, - x2 = (prevOffSet.x + endX) / 2, - y2 = (prevOffSet.y + endY) / 2, - ) - prevOffSet = Offset(endX, endY) - } - - override fun shouldDraw(): Boolean { - return true - } -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/LineShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/LineShape.kt deleted file mode 100644 index b52b9b0..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/LineShape.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.DrawScope - -class LineShape( - color: Color? = null, - width: Float? = null, - alpha: Float? = null -) : AbstractShape() { - - init { - updatePaintValues(color, width, alpha) - } - - private var startOffset: Offset = Offset.Unspecified - private var endOffset: Offset = Offset.Unspecified - - override fun draw(drawScope: DrawScope) { - drawScope.drawLine( - start = startOffset, - end = endOffset, - brush = SolidColor(mColor), - strokeWidth = mWidth, - cap = StrokeCap.Round, - alpha = mAlpha - ) - } - - override fun initShape(startX: Float, startY: Float) { - startOffset = Offset(startX, startY) - } - - override fun moveShape(endX: Float, endY: Float) { - endOffset = Offset(endX, endY) - } - - override fun shouldDraw(): Boolean { - return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified - } -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/OvalShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/OvalShape.kt deleted file mode 100644 index c81c66f..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/OvalShape.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke - -class OvalShape( - color: Color? = null, - width: Float? = null, - alpha: Float? = null -): AbstractShape() { - - init { - updatePaintValues(color, width, alpha) - } - - private var startOffset: Offset = Offset.Unspecified - private var endOffset: Offset = Offset.Unspecified - - override fun draw(drawScope: DrawScope) { - drawScope.drawOval( - topLeft = startOffset, - size = Size( - width = (endOffset.x - startOffset.x), - height = (endOffset.y - startOffset.y) - ), - brush = SolidColor(mColor), - style = Stroke( - width = mWidth, - cap = StrokeCap.Round, - join = StrokeJoin.Round - ), - alpha = mAlpha - ) - } - - override fun initShape(startX: Float, startY: Float) { - startOffset = Offset(startX, startY) - } - - override fun moveShape(endX: Float, endY: Float) { - endOffset = Offset(endX, endY) - } - - override fun shouldDraw(): Boolean { - return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified - } -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/RectangleShape.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/RectangleShape.kt deleted file mode 100644 index bb50bf1..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/RectangleShape.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke - -class RectangleShape( - color: Color? = null, - width: Float? = null, - alpha: Float? = null -): AbstractShape() { - - init { - updatePaintValues(color, width, alpha) - } - - private var startOffset: Offset = Offset.Unspecified - private var endOffset: Offset = Offset.Unspecified - - override fun draw(drawScope: DrawScope) { - drawScope.drawRect( - topLeft = startOffset, - size = Size( - width = (endOffset.x - startOffset.x), - height = (endOffset.y - startOffset.y) - ), - brush = SolidColor(mColor), - style = Stroke( - width = mWidth, - cap = StrokeCap.Round, - join = StrokeJoin.Round - ), - alpha = mAlpha - ) - } - - override fun initShape(startX: Float, startY: Float) { - startOffset = Offset(startX, startY) - } - - override fun moveShape(endX: Float, endY: Float) { - endOffset = Offset(endX, endY) - } - - override fun shouldDraw(): Boolean { - return startOffset != Offset.Unspecified && endOffset != Offset.Unspecified - } -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/ShapeType.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/ShapeType.kt deleted file mode 100644 index 99cf092..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/drawingTool/shapes/ShapeType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes - -enum class ShapeType { - LINE, OVAL, RECTANGLE -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/models/PathDetails.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/models/PathDetails.kt deleted file mode 100644 index d8ba3ae..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/drawingCanvas/models/PathDetails.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models - -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.AbstractShape - -data class PathDetails( - val drawingShape: AbstractShape, -) \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeEvent.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeEvent.kt deleted file mode 100644 index 6c4da2b..0000000 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/stateHandling/DrawModeEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.abizer_r.quickedit.ui.drawMode.stateHandling - -import androidx.compose.ui.graphics.Color -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.models.PathDetails - - -sealed class DrawModeEvent { - data class AddNewPath(val pathDetail: PathDetails): DrawModeEvent() - data class ToggleColorPicker(val selectedColor: Color?): DrawModeEvent() - data class UpdateToolbarExtensionVisibility(val isVisible: Boolean): DrawModeEvent() - object OnUndo: DrawModeEvent() - object OnRedo: DrawModeEvent() -} \ No newline at end of file diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/toptoolbar/DrawModeTopToolbar.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/toptoolbar/DrawModeTopToolbar.kt index fb2c3b7..5b99bed 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/toptoolbar/DrawModeTopToolbar.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/drawMode/toptoolbar/DrawModeTopToolbar.kt @@ -25,8 +25,8 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreen.kt index bca5b1e..b7163d5 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/EditorScreen.kt @@ -31,11 +31,11 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.abizer_r.quickedit.utils.ImmutableList -import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer -import com.abizer_r.quickedit.ui.common.bottomToolbarModifier -import com.abizer_r.quickedit.ui.common.topToolbarModifier +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.bottomToolbarModifier +import io.github.abizerr.quickedit.ui.common.topToolbarModifier import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.BottomToolBarStatic import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_MEDIUM import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL @@ -45,9 +45,9 @@ import com.abizer_r.quickedit.ui.editorScreen.topToolbar.EditorTopToolBar import com.abizer_r.quickedit.utils.AppUtils import com.abizer_r.quickedit.utils.FileUtils import com.abizer_r.quickedit.utils.editorScreen.EditorScreenUtils -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.BitmapUtils -import com.abizer_r.quickedit.utils.toast +import io.github.abizerr.quickedit.ui.utils.toast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/BottomToolbar.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/BottomToolbar.kt index 9a0975f..85cb368 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/BottomToolbar.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/BottomToolbar.kt @@ -41,14 +41,14 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.utils.ImmutableList import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarEvent import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState -import com.abizer_r.quickedit.utils.defaultTextColor import com.abizer_r.quickedit.utils.drawMode.DrawModeUtils +import io.github.abizerr.quickedit.ui.utils.defaultTextColor import com.abizer_r.quickedit.utils.editorScreen.EditorScreenUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils import java.util.UUID diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarEvent.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarEvent.kt index 70af41d..184dc11 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarEvent.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarEvent.kt @@ -1,6 +1,6 @@ package com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType +import io.github.abizerr.quickedit.engine.drawspec.ShapeType sealed class BottomToolbarEvent { data class OnItemClicked(val toolbarItem: BottomToolbarItem): BottomToolbarEvent() diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarItem.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarItem.kt index 416f593..c5e253d 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarItem.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/bottomToolbar/state/BottomToolbarItem.kt @@ -3,7 +3,7 @@ package com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state import androidx.compose.runtime.Immutable import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType +import io.github.abizerr.quickedit.engine.drawspec.ShapeType import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/topToolbar/EditorTopToolbar.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/topToolbar/EditorTopToolbar.kt index a0f345d..f939e54 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/topToolbar/EditorTopToolbar.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/editorScreen/topToolbar/EditorTopToolbar.kt @@ -12,12 +12,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Redo import androidx.compose.material.icons.filled.SaveAlt -import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Undo import androidx.compose.material.icons.rounded.Share import androidx.compose.material3.MaterialTheme @@ -29,8 +26,8 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeScreen.kt index 4384ed7..e677a74 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeScreen.kt @@ -36,20 +36,20 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor -import com.abizer_r.quickedit.utils.defaultErrorToast -import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.defaultErrorToast +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer import com.abizer_r.quickedit.ui.common.LoadingView -import com.abizer_r.quickedit.ui.common.bottomToolbarModifier -import com.abizer_r.quickedit.ui.common.topToolbarModifier +import io.github.abizerr.quickedit.ui.common.bottomToolbarModifier +import io.github.abizerr.quickedit.ui.common.topToolbarModifier import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_EXTRA_LARGE import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectsPreviewListFullWidth import com.abizer_r.quickedit.utils.effectsMode.EffectsModeUtils -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap import com.smarttoolfactory.screenshot.ImageResult import com.smarttoolfactory.screenshot.ScreenshotBox diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/effectsPreview/EffectsPreviewListFullWidth.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/effectsPreview/EffectsPreviewListFullWidth.kt index fc3f7c0..64d75f8 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/effectsPreview/EffectsPreviewListFullWidth.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/effectsPreview/EffectsPreviewListFullWidth.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -29,19 +28,17 @@ import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.imageResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.Black_alpha_30 -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.Black_alpha_30 +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_EXTRA_LARGE import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_LARGE -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_MEDIUM -import com.abizer_r.quickedit.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.utils.defaultTextColor @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreen.kt index d4abe49..4cbaab0 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreen.kt @@ -1,13 +1,10 @@ package com.abizer_r.quickedit.ui.mainScreen import android.Manifest -import android.content.res.Configuration import android.graphics.Bitmap import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -16,18 +13,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.lifecycleScope -import com.abizer_r.quickedit.theme.QuickEditTheme import com.abizer_r.quickedit.ui.common.permission.PermissionDialog import com.abizer_r.quickedit.ui.common.permission.StoragePermissionTextProvider import com.abizer_r.quickedit.utils.FileUtils import com.abizer_r.quickedit.utils.PermissionUtils -import com.abizer_r.quickedit.utils.getActivity -import com.abizer_r.quickedit.utils.getOpenAppSettingsIntent +import io.github.abizerr.quickedit.ui.utils.getActivity +import io.github.abizerr.quickedit.ui.utils.getOpenAppSettingsIntent import com.abizer_r.quickedit.utils.other.bitmap.BitmapStatus import com.abizer_r.quickedit.utils.other.bitmap.BitmapUtils import kotlinx.coroutines.Dispatchers diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenButtonsLayout.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenButtonsLayout.kt index fd07d43..aade61f 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenButtonsLayout.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenButtonsLayout.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.abizer_r.quickedit.R import com.abizer_r.quickedit.ui.common.LoadingView -import com.abizer_r.quickedit.utils.toast +import io.github.abizerr.quickedit.ui.utils.toast @Composable fun MainScreenButtonsLayout( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenLayout.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenLayout.kt index 3c9e752..75c8851 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenLayout.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/mainScreen/MainScreenLayout.kt @@ -21,12 +21,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.abizer_r.quickedit.ui.common.AppIconWithName import com.abizer_r.quickedit.ui.common.ErrorView import com.abizer_r.quickedit.ui.common.PermissionDeniedView import com.abizer_r.quickedit.utils.PermissionUtils.PermissionTypes -import com.abizer_r.quickedit.utils.getOpenAppSettingsIntent +import io.github.abizerr.quickedit.ui.utils.getOpenAppSettingsIntent import com.abizer_r.quickedit.utils.other.bitmap.BitmapStatus @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditApp.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditApp.kt index c484571..6a420a9 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditApp.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditApp.kt @@ -10,9 +10,24 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.engine.api.EditImage +import io.github.abizerr.quickedit.engine.api.SaveFormat +import io.github.abizerr.quickedit.tool.crop.CropContribution +import io.github.abizerr.quickedit.tool.draw.DrawToolContribution +import io.github.abizerr.quickedit.tool.effects.EffectsContribution +import io.github.abizerr.quickedit.tool.text.TextContribution +import io.github.abizerr.quickedit.ui.api.QuickEditConfig +import io.github.abizerr.quickedit.ui.api.QuickEditEditor +import io.github.abizerr.quickedit.ui.utils.getDummyBitmap +/** + * Legacy entry kept for binary/source compatibility. + * Internally delegates to the new :quickedit-compose-ui editor. + * + */ @Composable fun QuickEditApp( initialImageUri: Uri? = null @@ -28,7 +43,35 @@ fun QuickEditApp( .padding(innerPadding) .background(MaterialTheme.colorScheme.background) ) { + // Old navigation (removed intentionally) QuickEditNavigation(initialImageUri) + + +// val editImage: EditImage? = initialImageUri?.let { +// EditImage.FromUri(it) +// } ?: EditImage.FromBitmap(getDummyBitmap()) +// +// val tools = remember { +// listOf( +// CropContribution(), +// DrawToolContribution(), +// TextContribution(), +// EffectsContribution() +// ) +// } +// +// QuickEditEditor( +// image = editImage, +// config = QuickEditConfig( +// tools = tools, +// maxUndo = 20, +// defaultFormat = SaveFormat.Jpeg(90) +// ), +//// state = QuickEditState(), +// onSave = { +// // TODO (revamp): map Result back if needed +// } +// ) } } } diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditNavigation.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditNavigation.kt index 0ee17e7..64a4a8a 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditNavigation.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/navigation/QuickEditNavigation.kt @@ -4,17 +4,9 @@ import android.graphics.Bitmap import android.net.Uri import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.tween -import androidx.compose.animation.slideIn -import androidx.compose.animation.slideOut import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.IntOffset import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -27,18 +19,11 @@ import com.abizer_r.quickedit.ui.editorScreen.EditorScreenState import com.abizer_r.quickedit.ui.effectsMode.EffectsModeScreen import com.abizer_r.quickedit.ui.mainScreen.MainScreen import com.abizer_r.quickedit.ui.textMode.TextModeScreen -import com.abizer_r.quickedit.utils.other.anim.AnimUtils -import com.abizer_r.quickedit.utils.other.anim.enterTransition -import com.abizer_r.quickedit.utils.other.anim.exitTransition -import com.abizer_r.quickedit.utils.other.anim.popEnterTransition -import com.abizer_r.quickedit.utils.other.anim.popExitTransition -import com.abizer_r.quickedit.utils.other.bitmap.BitmapUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch +import io.github.abizerr.quickedit.ui.utils.anim.enterTransition +import io.github.abizerr.quickedit.ui.utils.anim.exitTransition +import io.github.abizerr.quickedit.ui.utils.anim.popEnterTransition +import io.github.abizerr.quickedit.ui.utils.anim.popExitTransition @Composable fun QuickEditNavigation( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeScreen.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeScreen.kt index aa1f7f1..3ef8eba 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeScreen.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeScreen.kt @@ -1,7 +1,6 @@ package com.abizer_r.quickedit.ui.textMode import android.graphics.Bitmap -import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -34,12 +33,11 @@ import androidx.constraintlayout.compose.Dimension import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import com.abizer_r.quickedit.utils.ImmutableList import com.abizer_r.quickedit.utils.textMode.blurBackground.BlurBitmapBackground -import com.abizer_r.quickedit.utils.defaultErrorToast -import com.abizer_r.quickedit.ui.common.AnimatedToolbarContainer -import com.abizer_r.quickedit.ui.common.bottomToolbarModifier -import com.abizer_r.quickedit.ui.common.topToolbarModifier +import io.github.abizerr.quickedit.ui.utils.defaultErrorToast +import io.github.abizerr.quickedit.ui.common.AnimatedToolbarContainer +import io.github.abizerr.quickedit.ui.common.bottomToolbarModifier +import io.github.abizerr.quickedit.ui.common.topToolbarModifier import com.abizer_r.quickedit.ui.textMode.TextModeEvent.* import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.BottomToolBarStatic import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_MEDIUM @@ -48,14 +46,11 @@ import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarE import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.TextModeToolbarExtension -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr import com.abizer_r.quickedit.ui.textMode.textEditorLayout.TextEditorLayout import com.abizer_r.quickedit.ui.textMode.textEditorLayout.TextEditorState import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap -import com.abizer_r.quickedit.utils.textMode.TextModeUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils.BorderForSelectedViews import com.abizer_r.quickedit.utils.textMode.TextModeUtils.DrawAllTransformableViews import com.smarttoolfactory.screenshot.ImageResult diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeViewModel.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeViewModel.kt index d8af534..b023486 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeViewModel.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/TextModeViewModel.kt @@ -17,7 +17,7 @@ import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxSta import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxEvents import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxState import com.abizer_r.quickedit.utils.ImmutableList -import com.abizer_r.quickedit.utils.other.anim.AnimUtils +import io.github.abizerr.quickedit.ui.utils.anim.AnimUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils.isTextModeItem import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/TextModeToolbarExtension.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/TextModeToolbarExtension.kt index 2c63164..07eadf4 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/TextModeToolbarExtension.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/TextModeToolbarExtension.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.TextModeToolbarExtensionEvent.* import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions.FontFamilyOptions @@ -25,7 +25,7 @@ import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptio import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleOptions import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr import com.abizer_r.quickedit.utils.drawMode.DrawModeUtils -import com.abizer_r.quickedit.utils.textMode.FontUtils +import io.github.abizerr.quickedit.ui.utils.font.FontUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils /** diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontFamilyOptions.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontFamilyOptions.kt index fdff2d6..b342d43 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontFamilyOptions.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/fontFamilyOptions/FontFamilyOptions.kt @@ -1,56 +1,31 @@ package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.fontFamilyOptions import android.content.res.Configuration -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft -import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight -import androidx.compose.material.icons.filled.FormatAlignCenter -import androidx.compose.material.icons.filled.FormatBold -import androidx.compose.material.icons.filled.FormatItalic -import androidx.compose.material.icons.filled.FormatStrikethrough -import androidx.compose.material.icons.filled.FormatUnderlined import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor -import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem -import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions.TextAlignOptions -import com.abizer_r.quickedit.utils.ImmutableList -import com.abizer_r.quickedit.utils.defaultTextColor -import com.abizer_r.quickedit.utils.textMode.FontUtils -import com.abizer_r.quickedit.utils.textMode.TextModeUtils +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.utils.font.FontItem +import io.github.abizerr.quickedit.ui.utils.defaultTextColor +import io.github.abizerr.quickedit.ui.utils.font.FontUtils @Composable fun FontFamilyOptions( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/alignmentOptions/AlignmentOptions.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/alignmentOptions/AlignmentOptions.kt index 0799ed2..fb58fa2 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/alignmentOptions/AlignmentOptions.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/alignmentOptions/AlignmentOptions.kt @@ -1,36 +1,25 @@ package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions import android.content.res.Configuration -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight import androidx.compose.material.icons.filled.FormatAlignCenter -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleOptions import com.abizer_r.quickedit.utils.textMode.TextModeUtils @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseOptions.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseOptions.kt index c1558cf..c4a914b 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseOptions.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/caseOptions/TextCaseOptions.kt @@ -1,46 +1,23 @@ package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions import android.content.res.Configuration -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft -import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight -import androidx.compose.material.icons.filled.FormatAlignCenter -import androidx.compose.material.icons.filled.FormatBold -import androidx.compose.material.icons.filled.FormatItalic -import androidx.compose.material.icons.filled.FormatStrikethrough -import androidx.compose.material.icons.filled.FormatUnderlined -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem -import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.alignmentOptions.TextAlignOptions -import com.abizer_r.quickedit.utils.textMode.TextModeUtils @Composable fun TextCaseOptions( diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleOptions.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleOptions.kt index e74ec79..c022479 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleOptions.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/bottomToolbarExtension/textFormatOptions/styleOptions/TextStyleOptions.kt @@ -1,36 +1,26 @@ package com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions import android.content.res.Configuration -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FormatBold import androidx.compose.material.icons.filled.FormatItalic import androidx.compose.material.icons.filled.FormatStrikethrough import androidx.compose.material.icons.filled.FormatUnderlined -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.common.toolbar.SelectableToolbarItem @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorLayout.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorLayout.kt index f687d4b..99da489 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorLayout.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/textEditorLayout/TextEditorLayout.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -23,7 +22,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -33,7 +31,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar import com.abizer_r.quickedit.utils.textMode.colorList.ColorListFullWidth diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/topToolbar/TextModeTopToolbar.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/topToolbar/TextModeTopToolbar.kt index e0b9d61..da960cb 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/topToolbar/TextModeTopToolbar.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/textMode/topToolbar/TextModeTopToolbar.kt @@ -21,8 +21,8 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/TransformableTextBox.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/TransformableTextBox.kt index c119794..7491435 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/TransformableTextBox.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/TransformableTextBox.kt @@ -10,19 +10,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextDecoration.* import com.abizer_r.quickedit.ui.transformableViews.base.TransformableTextBoxState import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBox import com.abizer_r.quickedit.ui.transformableViews.base.TransformableBoxEvents -import com.abizer_r.quickedit.utils.textMode.FontUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils.getDefaultEditorTextStyle @Composable diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBox.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBox.kt index d73c996..6996007 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBox.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBox.kt @@ -35,7 +35,6 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -45,7 +44,7 @@ import androidx.compose.ui.unit.coerceIn import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize import androidx.constraintlayout.compose.ConstraintLayout -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.abizer_r.quickedit.ui.transformableViews.TransformableTextBox import com.abizer_r.quickedit.utils.drawMode.DrawModeUtils import com.abizer_r.quickedit.utils.drawMode.pxToDp diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxState.kt b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxState.kt index 32d717f..fd901e0 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxState.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/ui/transformableViews/base/TransformableBoxState.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.TextUnit import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.caseOptions.TextCaseType import com.abizer_r.quickedit.ui.textMode.bottomToolbarExtension.textFormatOptions.styleOptions.TextStyleAttr -import com.abizer_r.quickedit.utils.textMode.FontUtils +import io.github.abizerr.quickedit.ui.utils.font.FontUtils import com.abizer_r.quickedit.utils.textMode.TextModeUtils abstract class TransformableBoxState { diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawModeUtils.kt b/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawModeUtils.kt index 117e133..cf2c8bd 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawModeUtils.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/utils/drawMode/DrawModeUtils.kt @@ -2,16 +2,12 @@ package com.abizer_r.quickedit.utils.drawMode import androidx.compose.runtime.Composable import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.BrushShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.AbstractShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.LineShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.OvalShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.RectangleShape -import com.abizer_r.quickedit.ui.drawMode.drawingCanvas.drawingTool.shapes.ShapeType import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.state.BottomToolbarItem +import io.github.abizerr.quickedit.engine.drawspec.ShapeType +import io.github.abizerr.quickedit.tool.draw.models.DrawToolItem +import io.github.abizerr.quickedit.tool.draw.util.DrawingConstants import kotlin.math.cos import kotlin.math.sin @@ -19,6 +15,25 @@ object DrawModeUtils { const val DEFAULT_SELECTED_INDEX = 2 + fun getDefaultDrawToolItemsList(): ArrayList { + return arrayListOf( + DrawToolItem.ColorItem, + DrawToolItem.PanItem, + DrawToolItem.BrushTool( + width = DrawingConstants.DEFAULT_STROKE_WIDTH, + opacity = DrawingConstants.DEFAULT_STROKE_OPACITY + ), + DrawToolItem.ShapeTool( + width = DrawingConstants.DEFAULT_STROKE_WIDTH, + opacity = DrawingConstants.DEFAULT_STROKE_OPACITY, + shapeType = ShapeType.LINE + ), + DrawToolItem.EraserTool( + width = DrawingConstants.DEFAULT_STROKE_WIDTH + ), + ) + } + fun getDefaultBottomToolbarItemsList(): ArrayList { return arrayListOf( BottomToolbarItem.ColorItem, @@ -57,105 +72,6 @@ object DrawModeUtils { } -fun BottomToolbarItem.getShape( - selectedColor: Color, - scale: Float = 1f, -): AbstractShape { - return when (val toolbarItem = this) { - is BottomToolbarItem.BrushTool -> { - BrushShape( - color = selectedColor, - width = toolbarItem.width / scale, - alpha = toolbarItem.opacity / 100f - ) - } - - is BottomToolbarItem.ShapeTool -> when (toolbarItem.shapeType) { - ShapeType.LINE -> LineShape( - color = selectedColor, - width = toolbarItem.width / scale, - alpha = toolbarItem.opacity / 100f - ) - - ShapeType.OVAL -> OvalShape( - color = selectedColor, - width = toolbarItem.width / scale, - alpha = toolbarItem.opacity / 100f - ) - - ShapeType.RECTANGLE -> RectangleShape( - color = selectedColor, - width = toolbarItem.width / scale, - alpha = toolbarItem.opacity / 100f - ) - } - - else -> { - /** - * this else block represents the EraserTool, as any other item (ColorItem, PanItem) won't be sent here - */ - val width = - if (toolbarItem is BottomToolbarItem.EraserTool) toolbarItem.width - else DrawingConstants.DEFAULT_STROKE_WIDTH - BrushShape( - isEraser = true, - width = width / scale - ) - } - } -} - -fun BottomToolbarItem.getWidthOrNull(): Float? { - return when (this) { - is BottomToolbarItem.BrushTool -> this.width - is BottomToolbarItem.EraserTool -> this.width - is BottomToolbarItem.ShapeTool -> this.width - else -> null - } -} - -fun BottomToolbarItem.setWidthIfPossible(mWidth: Float): BottomToolbarItem { - when (this) { - is BottomToolbarItem.BrushTool -> this.width = mWidth - is BottomToolbarItem.EraserTool -> this.width = mWidth - is BottomToolbarItem.ShapeTool -> this.width = mWidth - else -> {} - } - return this -} - -fun BottomToolbarItem.getOpacityOrNull(): Float? { - return when (this) { - is BottomToolbarItem.BrushTool -> this.opacity - is BottomToolbarItem.ShapeTool -> this.opacity - else -> null - } -} - -fun BottomToolbarItem.setOpacityIfPossible(mOpacity: Float): BottomToolbarItem { - when (this) { - is BottomToolbarItem.BrushTool -> this.opacity = mOpacity - is BottomToolbarItem.ShapeTool -> this.opacity = mOpacity - else -> {} - } - return this -} - -fun BottomToolbarItem.getShapeTypeOrNull(): ShapeType? { - return when (this) { - is BottomToolbarItem.ShapeTool -> this.shapeType - else -> null - } -} - -fun BottomToolbarItem.setShapeTypeIfPossible(mShapeType: ShapeType): BottomToolbarItem { - when (this) { - is BottomToolbarItem.ShapeTool -> this.shapeType = mShapeType - else -> {} - } - return this -} - @Composable fun Dp.toPx(): Float { return with(LocalDensity.current) { diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/blurBackground/BlurBitmapBackground.kt b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/blurBackground/BlurBitmapBackground.kt index f276aeb..e14bd10 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/blurBackground/BlurBitmapBackground.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/blurBackground/BlurBitmapBackground.kt @@ -10,12 +10,11 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.imageResource import androidx.compose.ui.tooling.preview.Preview import com.abizer_r.quickedit.R -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme import com.skydoves.cloudy.cloudy @OptIn(ExperimentalComposeUiApi::class) diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/ColorListFullWidth.kt b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/ColorListFullWidth.kt index 94c5e6e..2846f4a 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/ColorListFullWidth.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/ColorListFullWidth.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme -import com.abizer_r.quickedit.theme.ToolBarBackgroundColor +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.ToolBarBackgroundColor import com.abizer_r.quickedit.utils.ColorUtils import com.abizer_r.quickedit.utils.ImmutableList diff --git a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/SelectableColor.kt b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/SelectableColor.kt index f68ef44..ca11548 100644 --- a/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/SelectableColor.kt +++ b/quickedit/src/main/java/com/abizer_r/quickedit/utils/textMode/colorList/SelectableColor.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.abizer_r.quickedit.theme.QuickEditTheme +import io.github.abizerr.quickedit.ui.theme.QuickEditTheme /** * The total size of this item is the adding of [itemSize] and [selectedBorderWidth] diff --git a/quickedit/src/main/res/font/edu_vicwant_bold.ttf b/quickedit/src/main/res/font/edu_vicwant_bold.ttf deleted file mode 100644 index 316d7a4..0000000 Binary files a/quickedit/src/main/res/font/edu_vicwant_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/matemasie_regular.ttf b/quickedit/src/main/res/font/matemasie_regular.ttf deleted file mode 100644 index 63e3a0b..0000000 Binary files a/quickedit/src/main/res/font/matemasie_regular.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/moderustic_bold.ttf b/quickedit/src/main/res/font/moderustic_bold.ttf deleted file mode 100644 index d920e4f..0000000 Binary files a/quickedit/src/main/res/font/moderustic_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/montserrat_bold.ttf b/quickedit/src/main/res/font/montserrat_bold.ttf deleted file mode 100644 index 0927b81..0000000 Binary files a/quickedit/src/main/res/font/montserrat_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/montserrat_bold_italic.ttf b/quickedit/src/main/res/font/montserrat_bold_italic.ttf deleted file mode 100644 index 02f5784..0000000 Binary files a/quickedit/src/main/res/font/montserrat_bold_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/montserrat_italic.ttf b/quickedit/src/main/res/font/montserrat_italic.ttf deleted file mode 100644 index cff3ceb..0000000 Binary files a/quickedit/src/main/res/font/montserrat_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/montserrat_regular.ttf b/quickedit/src/main/res/font/montserrat_regular.ttf deleted file mode 100644 index f4a266d..0000000 Binary files a/quickedit/src/main/res/font/montserrat_regular.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/oswald_bold.ttf b/quickedit/src/main/res/font/oswald_bold.ttf deleted file mode 100644 index b9c6e37..0000000 Binary files a/quickedit/src/main/res/font/oswald_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/oswald_regular.ttf b/quickedit/src/main/res/font/oswald_regular.ttf deleted file mode 100644 index 5903df4..0000000 Binary files a/quickedit/src/main/res/font/oswald_regular.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/playwrite_italic.ttf b/quickedit/src/main/res/font/playwrite_italic.ttf deleted file mode 100644 index e27a6a4..0000000 Binary files a/quickedit/src/main/res/font/playwrite_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/playwrite_regular.ttf b/quickedit/src/main/res/font/playwrite_regular.ttf deleted file mode 100644 index 73e55d7..0000000 Binary files a/quickedit/src/main/res/font/playwrite_regular.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/poppins_bold_italic.ttf b/quickedit/src/main/res/font/poppins_bold_italic.ttf deleted file mode 100644 index e61e8e8..0000000 Binary files a/quickedit/src/main/res/font/poppins_bold_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/poppins_italic.ttf b/quickedit/src/main/res/font/poppins_italic.ttf deleted file mode 100644 index 12b7b3c..0000000 Binary files a/quickedit/src/main/res/font/poppins_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/roboto_bold.ttf b/quickedit/src/main/res/font/roboto_bold.ttf deleted file mode 100644 index e64db79..0000000 Binary files a/quickedit/src/main/res/font/roboto_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/roboto_bold_italic.ttf b/quickedit/src/main/res/font/roboto_bold_italic.ttf deleted file mode 100644 index 5e39ae9..0000000 Binary files a/quickedit/src/main/res/font/roboto_bold_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/roboto_italic.ttf b/quickedit/src/main/res/font/roboto_italic.ttf deleted file mode 100644 index 65498ee..0000000 Binary files a/quickedit/src/main/res/font/roboto_italic.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/roboto_regular.ttf b/quickedit/src/main/res/font/roboto_regular.ttf deleted file mode 100644 index 2d116d9..0000000 Binary files a/quickedit/src/main/res/font/roboto_regular.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/teko_bold.ttf b/quickedit/src/main/res/font/teko_bold.ttf deleted file mode 100644 index f9f1e84..0000000 Binary files a/quickedit/src/main/res/font/teko_bold.ttf and /dev/null differ diff --git a/quickedit/src/main/res/font/teko_regular.ttf b/quickedit/src/main/res/font/teko_regular.ttf deleted file mode 100644 index 553ac65..0000000 Binary files a/quickedit/src/main/res/font/teko_regular.ttf and /dev/null differ diff --git a/settings.gradle.kts b/settings.gradle.kts index a9a9ccb..5d50d24 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,5 +15,11 @@ dependencyResolutionManagement { } rootProject.name = "QuickEdit" -include(":app") -include(":quickedit") \ No newline at end of file +include(":app-sample") +include(":quickedit") +include(":quickedit-core-engine") +include(":quickedit-ui") +include(":quickedit-tool-draw") +include(":quickedit-tool-text") +include(":quickedit-tool-crop") +include(":quickedit-tool-effects")