├── .ci-java-version ├── .editorconfig ├── .github └── workflows │ ├── pr-package-lock.yml │ ├── pr.yml │ ├── publish_release.yml │ └── publish_snapshot.yml ├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── Dangerfile.df.kts ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle.kts ├── changelog_config.json ├── detekt.yml ├── format ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store └── package-lock.json ├── library ├── build.gradle.kts ├── gradle.properties └── src │ └── commonMain │ └── kotlin │ └── com │ └── eygraber │ └── compose │ └── colorpicker │ ├── ColorPicker.kt │ ├── ColorWheel.kt │ ├── Magnifier.kt │ ├── MagnifierTransitionData.kt │ └── Utils.kt ├── renovate.json ├── sample ├── android-app │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── eygraber │ │ │ └── compose │ │ │ └── colorpicker │ │ │ └── sample │ │ │ └── SampleActivity.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml ├── jvm-app │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── eygraber │ │ └── compose │ │ └── colorpicker │ │ └── sample │ │ └── ColorPickerJvm.kt ├── shared │ ├── build.gradle.kts │ └── src │ │ ├── androidMain │ │ └── kotlin │ │ │ └── com │ │ │ └── eygraber │ │ │ └── compose │ │ │ └── colorpicker │ │ │ └── sample │ │ │ └── FixedDecimalCount.android.kt │ │ ├── commonMain │ │ └── kotlin │ │ │ └── com │ │ │ └── eygraber │ │ │ └── compose │ │ │ └── colorpicker │ │ │ └── sample │ │ │ ├── FixedDecimalCount.common.kt │ │ │ ├── RefreshIcon.kt │ │ │ └── Sample.kt │ │ ├── jsMain │ │ └── kotlin │ │ │ └── com │ │ │ └── eygraber │ │ │ └── compose │ │ │ └── colorpicker │ │ │ └── sample │ │ │ └── FixedDecimalCount.js.kt │ │ ├── jvmMain │ │ └── kotlin │ │ │ └── com │ │ │ └── eygraber │ │ │ └── compose │ │ │ └── colorpicker │ │ │ └── sample │ │ │ └── FixedDecimalCount.jvm.kt │ │ └── wasmJsMain │ │ └── kotlin │ │ └── com │ │ └── eygraber │ │ └── compose │ │ └── colorpicker │ │ └── sample │ │ └── FixedDecimalCount.wasm.kt └── webApp │ ├── build.gradle.kts │ ├── detekt.yml │ └── src │ └── wasmJsMain │ ├── kotlin │ └── main.wasm.kt │ └── resources │ ├── index.html │ └── load.mjs └── settings.gradle.kts /.ci-java-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # noinspection EditorConfigKeyCorrectness 2 | [*.{kt,kts}] 3 | ij_kotlin_allow_trailing_comma = true 4 | ij_kotlin_allow_trailing_comma_on_call_site = true 5 | ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^ 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | ktlint_code_style = android_studio 10 | ktlint_experimental = enabled 11 | ktlint_ignore_back_ticked_identifier = true 12 | ktlint_standard_annotation = disabled 13 | ktlint_standard_blank-line-between-when-conditions = disabled 14 | ktlint_standard_class-signature = disabled 15 | ktlint_standard_comment-wrapping = disabled 16 | # disabled because of https://github.com/pinterest/ktlint/issues/2182#issuecomment-1863408507 17 | ktlint_standard_condition-wrapping = disabled 18 | ktlint_standard_filename = disabled 19 | ktlint_standard_function-naming = disabled 20 | ktlint_standard_function-signature = disabled 21 | ktlint_standard_keyword-spacing = disabled 22 | ktlint_standard_package-name = disabled 23 | ktlint_standard_property-naming = disabled 24 | ktlint_standard_spacing-between-declarations-with-annotations = disabled 25 | max_line_length = 120 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-package-lock.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade Package Lock 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | upgrade_package_lock: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | repository: ${{ github.event.pull_request.head.repo.full_name }} 13 | ref: ${{ github.event.pull_request.head.ref }} 14 | token: ${{ secrets.PUSH_PAT }} 15 | 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: 'zulu' 19 | java-version-file: .ci-java-version 20 | 21 | - name: Setup Gradle 22 | uses: gradle/actions/setup-gradle@v4 23 | with: 24 | gradle-version: wrapper 25 | 26 | - name: Run assemble task 27 | run: ./gradlew kotlinUpgradePackageLock --rerun-tasks 28 | 29 | - name: Commit package lock changes 30 | id: commit_package_lock_changes 31 | uses: EndBug/add-and-commit@v9 32 | with: 33 | add: "['kotlin-js-store/package-lock.json']" 34 | default_author: github_actions 35 | message: "Upgrade package lock" 36 | 37 | env: 38 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx16g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1024m" 39 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | danger: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Danger 13 | uses: danger/kotlin@1.3.3 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | detekt: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: actions/setup-java@v4 23 | with: 24 | distribution: 'zulu' 25 | java-version-file: .ci-java-version 26 | 27 | - name: Setup Gradle 28 | uses: gradle/actions/setup-gradle@v4 29 | with: 30 | gradle-version: wrapper 31 | 32 | - name: Run detekt 33 | run: ./gradlew detektAll 34 | 35 | ktlint: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Run ktlint 41 | run: ./format --no-format 42 | 43 | lint: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - uses: actions/setup-java@v4 49 | with: 50 | distribution: 'zulu' 51 | java-version-file: .ci-java-version 52 | 53 | - name: Setup Gradle 54 | uses: gradle/actions/setup-gradle@v4 55 | with: 56 | gradle-version: wrapper 57 | 58 | - name: Run checks 59 | run: ./gradlew lintRelease 60 | 61 | test: 62 | strategy: 63 | matrix: 64 | os: [ macos-latest, ubuntu-latest ] 65 | runs-on: ${{matrix.os}} 66 | steps: 67 | - uses: actions/checkout@v4 68 | 69 | - uses: actions/setup-java@v4 70 | with: 71 | distribution: 'zulu' 72 | java-version-file: .ci-java-version 73 | 74 | - name: Setup Gradle 75 | uses: gradle/actions/setup-gradle@v4 76 | with: 77 | gradle-version: wrapper 78 | 79 | - name: Run tests 80 | run: ./gradlew allTests 81 | if: matrix.os == 'ubuntu-latest' 82 | 83 | - name: Run Apple tests 84 | run: ./gradlew iosX64Test 85 | if: matrix.os == 'macos-latest' 86 | 87 | env: 88 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx16g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1024m" 89 | -------------------------------------------------------------------------------- /.github/workflows/publish_release.yml: -------------------------------------------------------------------------------- 1 | name: Publish a release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | VERSION_FILE: gradle.properties 8 | VERSION_EXTRACT_PATTERN: '(?<=version=).+' 9 | GH_USER_NAME: github.actor 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx16g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1024m" 11 | 12 | jobs: 13 | publish_artifacts: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | token: ${{ secrets.PUSH_PAT }} 20 | 21 | - name: Generate versions 22 | uses: HardNorth/github-version-generate@v1 23 | with: 24 | version-source: file 25 | version-file: ${{ env.VERSION_FILE }} 26 | version-file-extraction-pattern: ${{ env.VERSION_EXTRACT_PATTERN }} 27 | 28 | - uses: actions/setup-java@v4 29 | with: 30 | distribution: 'zulu' 31 | java-version-file: .ci-java-version 32 | 33 | - name: Setup Gradle 34 | uses: gradle/actions/setup-gradle@v4 35 | with: 36 | gradle-version: wrapper 37 | 38 | - name: Publish the artifacts 39 | env: 40 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 41 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 42 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }} 43 | run: ./gradlew publishAllPublicationsToMavenCentralRepository -Pversion=${{ env.RELEASE_VERSION }} 44 | 45 | - name: Create, checkout, and push release branch 46 | run: | 47 | git config user.name eygraber 48 | git config user.email 1100381+eygraber@users.noreply.github.com 49 | git checkout -b releases/${{ env.RELEASE_VERSION }} 50 | git push origin releases/${{ env.RELEASE_VERSION }} 51 | 52 | - name: Import GPG Key 53 | uses: crazy-max/ghaction-import-gpg@v6 54 | with: 55 | gpg_private_key: ${{ secrets.GIT_SIGNING_PRIVATE_KEY }} 56 | passphrase: ${{ secrets.GIT_SIGNING_PRIVATE_KEY_PASSWORD }} 57 | git_user_signingkey: true 58 | git_commit_gpgsign: true 59 | git_tag_gpgsign: true 60 | 61 | - name: Store SHA of HEAD commit on ENV 62 | run: echo "GIT_HEAD=$(git rev-parse HEAD)" >> $GITHUB_ENV 63 | 64 | - name: Create tag 65 | id: create_tag 66 | uses: actions/github-script@v7 67 | with: 68 | github-token: ${{ secrets.PUSH_PAT }} 69 | script: | 70 | const {GIT_HEAD} = process.env 71 | github.rest.git.createRef({ 72 | owner: context.repo.owner, 73 | repo: context.repo.repo, 74 | ref: "refs/tags/${{ env.RELEASE_VERSION }}", 75 | sha: `${GIT_HEAD}` 76 | }) 77 | 78 | - name: Build changelog 79 | id: build_changelog 80 | uses: mikepenz/release-changelog-builder-action@v5 81 | with: 82 | configuration: "changelog_config.json" 83 | toTag: ${{ env.RELEASE_VERSION }} 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | 87 | - name: Create release 88 | id: create_release 89 | uses: ncipollo/release-action@v1 90 | with: 91 | body: ${{ steps.build_changelog.outputs.changelog }} 92 | name: Release ${{ env.RELEASE_VERSION }} 93 | tag: ${{ env.RELEASE_VERSION }} 94 | token: ${{ secrets.PUSH_PAT }} 95 | 96 | - uses: actions/checkout@v4 97 | with: 98 | ref: ${{ github.event.head_ref }} 99 | token: ${{ secrets.PUSH_PAT }} 100 | 101 | - name: Prepare next dev version 102 | id: prepare_next_dev 103 | run: | 104 | sed -i -e 's/${{ env.CURRENT_VERSION }}/${{ env.NEXT_VERSION }}/g' gradle.properties && \ 105 | sed -i -E -e 's/compose-color-picker(:|\/)[0-9]+\.[0-9]+\.[0-9]+/compose-color-picker\1${{ env.RELEASE_VERSION }}/g' README.md 106 | 107 | - name: Update package-lock.json if needed 108 | run: ./gradlew kotlinUpgradePackageLock --no-build-cache --no-configuration-cache --rerun-tasks 109 | 110 | - name: Commit next dev version 111 | id: commit_next_dev 112 | uses: EndBug/add-and-commit@v9 113 | with: 114 | add: "['gradle.properties', 'README.md', 'kotlin-js-store/package-lock.json']" 115 | default_author: github_actions 116 | message: "Prepare next dev version" 117 | -------------------------------------------------------------------------------- /.github/workflows/publish_snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish a snapshot release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish_snapshot: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - uses: actions/setup-java@v4 15 | with: 16 | distribution: 'zulu' 17 | java-version-file: .ci-java-version 18 | 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@v4 21 | with: 22 | gradle-version: wrapper 23 | dependency-graph: generate-and-submit 24 | 25 | - name: Publish the artifacts 26 | env: 27 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 28 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 29 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }} 30 | run: ./gradlew publishAllPublicationsToMavenCentralRepository 31 | 32 | env: 33 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx16g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=1024m" 34 | DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS: runtimeClasspath|releaseRuntimeClasspath 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .kotlin 4 | /local.properties 5 | /.idea 6 | # Make an exception for the code style 7 | !.idea/codeStyles/ 8 | .DS_Store 9 | build/ 10 | /captures 11 | .externalNativeBuild 12 | .cxx 13 | tmp 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1343 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /Dangerfile.df.kts: -------------------------------------------------------------------------------- 1 | import systems.danger.kotlin.* 2 | import java.util.Locale 3 | 4 | danger(args) { 5 | with(github) { 6 | val labelNames = issue.labels.map { it.name }.toSet() 7 | 8 | /* 9 | # -------------------------------------------------------------------------------------------------------------------- 10 | # Check if labels were added to the pull request 11 | #-------------------------------------------------------------------------------------------------------------------- 12 | */ 13 | val labelsToFilter = setOf("hold", "skip release notes") 14 | val acceptableLabels = labelNames.filter { it !in labelsToFilter } 15 | 16 | if(acceptableLabels.isEmpty()) { 17 | fail("PR needs labels (hold and skip release notes don't count)") 18 | } 19 | 20 | /* 21 | # -------------------------------------------------------------------------------------------------------------------- 22 | # Don't merge if there is a WIP or Hold label applied 23 | # -------------------------------------------------------------------------------------------------------------------- 24 | */ 25 | if("Hold" in labelNames) fail("This PR cannot be merged with a hold label applied") 26 | 27 | /* 28 | # -------------------------------------------------------------------------------------------------------------------- 29 | # Check if merge commits were added to the pull request 30 | # -------------------------------------------------------------------------------------------------------------------- 31 | */ 32 | val mergeCommitRegex = Regex("^Merge branch '${pullRequest.base.ref}'.*") 33 | if(git.commits.any { it.message.matches(mergeCommitRegex) }) { 34 | fail("Please rebase to get rid of the merge commits in this PR") 35 | } 36 | } 37 | 38 | /* 39 | # -------------------------------------------------------------------------------------------------------------------- 40 | # Make sure that no crash files or dumps are in the commit 41 | # -------------------------------------------------------------------------------------------------------------------- 42 | */ 43 | val touchedFiles = git.createdFiles + git.modifiedFiles 44 | if(touchedFiles.any { it.startsWith("hs_err_pid") || it.startsWith("java_pid") }) { 45 | fail("Please remove any error logs (hs_err_pid*.log) or heap dumps (java_pid*.hprof)") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Eliezer Graber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This project makes use of a modified version of the ColorPickerDemo from AOSP 2 | 3 | Copyright 2020 The Android Open Source Project 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Under development 2 | 3 | Based on the [ColorPickerDemo](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ColorPickerDemo.kt) from the AOSP compose samples. 4 | 5 | ``` 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("com.eygraber:compose-color-picker:0.0.19") 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.eygraber.conventions.tasks.deleteRootBuildDirWhenCleaning 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | 4 | buildscript { 5 | dependencies { 6 | classpath(libs.buildscript.android) 7 | classpath(libs.buildscript.androidCacheFix) 8 | classpath(libs.buildscript.compose.compiler) 9 | classpath(libs.buildscript.compose.jetbrains) 10 | classpath(libs.buildscript.detekt) 11 | classpath(libs.buildscript.dokka) 12 | classpath(libs.buildscript.kotlin) 13 | classpath(libs.buildscript.publish) 14 | } 15 | } 16 | 17 | plugins { 18 | base 19 | alias(libs.plugins.conventions) 20 | } 21 | 22 | deleteRootBuildDirWhenCleaning() 23 | 24 | gradleConventionsDefaults { 25 | android { 26 | sdkVersions( 27 | compileSdk = libs.versions.android.sdk.compile, 28 | targetSdk = libs.versions.android.sdk.target, 29 | minSdk = libs.versions.android.sdk.min, 30 | ) 31 | } 32 | 33 | detekt { 34 | plugins( 35 | libs.detektCompose, 36 | libs.detektEygraber.formatting, 37 | libs.detektEygraber.style, 38 | ) 39 | } 40 | 41 | kotlin { 42 | jvmTargetVersion = JvmTarget.JVM_11 43 | } 44 | } 45 | 46 | gradleConventionsKmpDefaults { 47 | targets( 48 | KmpTarget.Android, 49 | KmpTarget.Ios, 50 | KmpTarget.Js, 51 | KmpTarget.Jvm, 52 | KmpTarget.WasmJs, 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /changelog_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": ["feature"] 6 | }, 7 | { 8 | "title": "## ⚙️ Chores", 9 | "labels": ["chore"] 10 | }, 11 | { 12 | "title": "## 🐛 Bugs", 13 | "labels": ["bug"] 14 | }, 15 | { 16 | "title": "## 🧪 Tests", 17 | "labels": ["test"] 18 | }, 19 | { 20 | "title": "## \uD83D\uDCD6 Documentation", 21 | "labels": ["documentation"] 22 | }, 23 | { 24 | "title": "\uD83D\uDCE6 Dependencies", 25 | "labels": ["dependencies"] 26 | } 27 | ], 28 | "ignore_labels": [ 29 | "duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix", "skip release notes", "hold" 30 | ], 31 | "sort": "ASC", 32 | "template": "${{CHANGELOG}}", 33 | "pr_template": "- ${{TITLE}} (#${{NUMBER}})", 34 | "empty_template": "- no changes", 35 | "label_extractor": [ 36 | { 37 | "pattern": "(.) (.+)", 38 | "target": "$1" 39 | }, 40 | { 41 | "pattern": "(.) (.+)", 42 | "target": "$1", 43 | "on_property": "title" 44 | } 45 | ], 46 | "transformers": [ 47 | { 48 | "pattern": "[\\-\\*] (\\[(...|TEST|CI|SKIP)\\])( )?(.+?)\n(.+?[\\-\\*] )(.+)", 49 | "target": "- $4\n - $6" 50 | } 51 | ], 52 | "max_tags_to_fetch": 200, 53 | "max_pull_requests": 200, 54 | "max_back_track_time_days": 365, 55 | "tag_resolver": { 56 | "method": "semver" 57 | }, 58 | "base_branches": [ 59 | "master" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 0 3 | excludeCorrectable: false 4 | weights: 5 | # complexity: 2 6 | # LongParameterList: 1 7 | # style: 1 8 | # comments: 1 9 | 10 | config: 11 | validation: true 12 | warningsAsErrors: true 13 | checkExhaustiveness: true 14 | 15 | processors: 16 | active: true 17 | exclude: 18 | # - 'FunctionCountProcessor' 19 | # - 'PropertyCountProcessor' 20 | # - 'ClassCountProcessor' 21 | # - 'PackageCountProcessor' 22 | # - 'KtFileCountProcessor' 23 | 24 | console-reports: 25 | active: true 26 | exclude: 27 | - 'ProjectStatisticsReport' 28 | - 'ComplexityReport' 29 | - 'NotificationReport' 30 | # - 'FindingsReport' 31 | - 'FileBasedFindingsReport' 32 | 33 | comments: 34 | active: false 35 | AbsentOrWrongFileLicense: 36 | active: false 37 | licenseTemplateFile: 'license.template' 38 | licenseTemplateIsRegex: false 39 | CommentOverPrivateFunction: 40 | active: false 41 | CommentOverPrivateProperty: 42 | active: false 43 | DeprecatedBlockTag: 44 | active: false 45 | EndOfSentenceFormat: 46 | active: false 47 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' 48 | UndocumentedPublicClass: 49 | active: false 50 | searchInNestedClass: true 51 | searchInInnerClass: true 52 | searchInInnerObject: true 53 | searchInInnerInterface: true 54 | UndocumentedPublicFunction: 55 | active: false 56 | UndocumentedPublicProperty: 57 | active: false 58 | 59 | complexity: 60 | active: true 61 | CognitiveComplexMethod: 62 | active: false 63 | ComplexCondition: 64 | active: true 65 | threshold: 4 66 | ComplexInterface: 67 | active: false 68 | threshold: 10 69 | includeStaticDeclarations: false 70 | includePrivateDeclarations: false 71 | CyclomaticComplexMethod: 72 | active: false 73 | threshold: 15 74 | ignoreSingleWhenExpression: false 75 | ignoreSimpleWhenEntries: false 76 | ignoreNestingFunctions: false 77 | nestingFunctions: ['run', 'let', 'apply', 'with', 'also', 'use', 'forEach', 'isNotNull', 'ifNull'] 78 | LabeledExpression: 79 | active: true 80 | ignoredLabels: [] 81 | LargeClass: 82 | active: false 83 | threshold: 600 84 | LongMethod: 85 | active: false 86 | threshold: 60 87 | LongParameterList: 88 | active: false 89 | functionThreshold: 6 90 | constructorThreshold: 7 91 | ignoreDefaultParameters: false 92 | ignoreDataClasses: true 93 | ignoreAnnotated: [] 94 | MethodOverloading: 95 | active: true 96 | threshold: 6 97 | NamedArguments: 98 | active: false 99 | threshold: 3 100 | NestedBlockDepth: 101 | active: true 102 | threshold: 6 103 | NestedScopeFunctions: 104 | active: false 105 | ReplaceSafeCallChainWithRun: 106 | active: false 107 | StringLiteralDuplication: 108 | active: false 109 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 110 | threshold: 3 111 | ignoreAnnotation: true 112 | excludeStringsWithLessThan5Characters: true 113 | ignoreStringsRegex: '$^' 114 | TooManyFunctions: 115 | active: false 116 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 117 | thresholdInFiles: 11 118 | thresholdInClasses: 11 119 | thresholdInInterfaces: 11 120 | thresholdInObjects: 11 121 | thresholdInEnums: 11 122 | ignoreDeprecated: false 123 | ignorePrivate: false 124 | ignoreOverridden: false 125 | 126 | coroutines: 127 | active: true 128 | GlobalCoroutineUsage: 129 | active: false 130 | InjectDispatcher: 131 | active: true 132 | RedundantSuspendModifier: 133 | active: true 134 | SleepInsteadOfDelay: 135 | active: true 136 | SuspendFunSwallowedCancellation: 137 | active: true 138 | SuspendFunWithCoroutineScopeReceiver: 139 | active: true 140 | SuspendFunWithFlowReturnType: 141 | active: true 142 | 143 | empty-blocks: 144 | active: true 145 | EmptyCatchBlock: 146 | active: true 147 | allowedExceptionNameRegex: '_|(ignore|expected).*' 148 | EmptyClassBlock: 149 | active: true 150 | EmptyDefaultConstructor: 151 | active: true 152 | EmptyDoWhileBlock: 153 | active: true 154 | EmptyElseBlock: 155 | active: true 156 | EmptyFinallyBlock: 157 | active: true 158 | EmptyForBlock: 159 | active: true 160 | EmptyFunctionBlock: 161 | active: false 162 | ignoreOverridden: false 163 | EmptyIfBlock: 164 | active: true 165 | EmptyInitBlock: 166 | active: true 167 | EmptyKtFile: 168 | active: true 169 | EmptySecondaryConstructor: 170 | active: true 171 | EmptyTryBlock: 172 | active: true 173 | EmptyWhenBlock: 174 | active: true 175 | EmptyWhileBlock: 176 | active: true 177 | 178 | exceptions: 179 | active: true 180 | ExceptionRaisedInUnexpectedLocation: 181 | active: true 182 | methodNames: ['toString', 'hashCode', 'equals', 'finalize'] 183 | InstanceOfCheckForException: 184 | active: false 185 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 186 | NotImplementedDeclaration: 187 | active: true 188 | ObjectExtendsThrowable: 189 | active: false 190 | PrintStackTrace: 191 | active: false 192 | RethrowCaughtException: 193 | active: true 194 | ReturnFromFinally: 195 | active: true 196 | ignoreLabeled: false 197 | SwallowedException: 198 | active: false 199 | ignoredExceptionTypes: 200 | - InterruptedException 201 | - NumberFormatException 202 | - ParseException 203 | - MalformedURLException 204 | allowedExceptionNameRegex: '_|(ignore|expected).*' 205 | ThrowingExceptionFromFinally: 206 | active: false 207 | ThrowingExceptionInMain: 208 | active: false 209 | ThrowingExceptionsWithoutMessageOrCause: 210 | active: false 211 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 212 | exceptions: 213 | - IllegalArgumentException 214 | - IllegalStateException 215 | - IOException 216 | ThrowingNewInstanceOfSameException: 217 | active: true 218 | TooGenericExceptionCaught: 219 | active: false 220 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 221 | exceptionNames: 222 | - ArrayIndexOutOfBoundsException 223 | - Error 224 | - Exception 225 | - IllegalMonitorStateException 226 | - NullPointerException 227 | - IndexOutOfBoundsException 228 | - RuntimeException 229 | - Throwable 230 | allowedExceptionNameRegex: '_|(ignore|expected).*' 231 | TooGenericExceptionThrown: 232 | active: false 233 | exceptionNames: 234 | - Error 235 | - Exception 236 | - Throwable 237 | - RuntimeException 238 | 239 | formatting-eygraber: 240 | NoWhitespaceAfterKeyword: 241 | active: true 242 | autoCorrect: true 243 | 244 | naming: 245 | active: true 246 | BooleanPropertyNaming: 247 | active: true 248 | allowedPattern: '^(is|has|are|should)' 249 | ClassNaming: 250 | active: true 251 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 252 | classPattern: '[A-Z][a-zA-Z0-9]*' 253 | ConstructorParameterNaming: 254 | active: true 255 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 256 | parameterPattern: '[a-z][A-Za-z0-9]*' 257 | privateParameterPattern: '[a-z][A-Za-z0-9]*' 258 | excludeClassPattern: '$^' 259 | EnumNaming: 260 | active: true 261 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 262 | enumEntryPattern: '([A-Z][a-z0-9]+)((\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?' 263 | ForbiddenClassName: 264 | active: false 265 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 266 | forbiddenName: [] 267 | FunctionMaxLength: 268 | active: false 269 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 270 | maximumFunctionNameLength: 30 271 | FunctionMinLength: 272 | active: false 273 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 274 | minimumFunctionNameLength: 3 275 | FunctionNaming: 276 | active: true 277 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 278 | functionPattern: '^([[a-z][A-Z]$][a-zA-Z$0-9]*)|(`.*`)$' 279 | excludeClassPattern: '$^' 280 | ignoreAnnotated: ['Composable'] 281 | FunctionParameterNaming: 282 | active: true 283 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 284 | parameterPattern: '[a-z][A-Za-z0-9]*' 285 | excludeClassPattern: '$^' 286 | InvalidPackageDeclaration: 287 | active: true 288 | excludes: ['**/*.kts'] 289 | rootPackage: '' 290 | LambdaParameterNaming: 291 | active: false 292 | MatchingDeclarationName: 293 | active: false 294 | mustBeFirst: true 295 | MemberNameEqualsClassName: 296 | active: true 297 | ignoreOverridden: true 298 | NoNameShadowing: 299 | active: true 300 | NonBooleanPropertyPrefixedWithIs: 301 | active: true 302 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 303 | ObjectPropertyNaming: 304 | active: true 305 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 306 | constantPattern: '[A-Z][_A-Z0-9]*' 307 | propertyPattern: '[A-Za-z][A-Za-z0-9]*' 308 | privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*' 309 | PackageNaming: 310 | active: false 311 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 312 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' 313 | TopLevelPropertyNaming: 314 | active: true 315 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 316 | constantPattern: '[A-Z][_A-Z0-9]*' 317 | propertyPattern: '[A-Za-z][A-Za-z0-9]*' 318 | privatePropertyPattern: '_?[A-Za-z][A-Za-z0-9]*' 319 | VariableMaxLength: 320 | active: false 321 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 322 | maximumVariableNameLength: 64 323 | VariableMinLength: 324 | active: false 325 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 326 | minimumVariableNameLength: 1 327 | VariableNaming: 328 | active: true 329 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 330 | variablePattern: '[a-z][A-Za-z0-9]*' 331 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' 332 | excludeClassPattern: '$^' 333 | 334 | performance: 335 | active: true 336 | ArrayPrimitive: 337 | active: true 338 | CouldBeSequence: 339 | active: true 340 | ForEachOnRange: 341 | active: true 342 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 343 | SpreadOperator: 344 | active: false 345 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 346 | UnnecessaryPartOfBinaryExpression: 347 | active: true 348 | UnnecessaryTemporaryInstantiation: 349 | active: true 350 | 351 | potential-bugs: 352 | active: true 353 | AvoidReferentialEquality: 354 | active: true 355 | CastNullableToNonNullableType: 356 | active: true 357 | CastToNullableType: 358 | active: false 359 | Deprecation: 360 | active: true 361 | DontDowncastCollectionTypes: 362 | active: false 363 | DoubleMutabilityForCollection: 364 | active: false 365 | ElseCaseInsteadOfExhaustiveWhen: 366 | active: true 367 | EqualsAlwaysReturnsTrueOrFalse: 368 | active: false 369 | EqualsWithHashCodeExist: 370 | active: true 371 | ExitOutsideMain: 372 | active: false 373 | ExplicitGarbageCollectionCall: 374 | active: true 375 | HasPlatformType: 376 | active: false 377 | IgnoredReturnValue: 378 | active: false 379 | restrictToConfig: true 380 | returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult'] 381 | ImplicitDefaultLocale: 382 | active: true 383 | ImplicitUnitReturnType: 384 | active: false 385 | allowExplicitReturnType: true 386 | InvalidRange: 387 | active: true 388 | IteratorHasNextCallsNextMethod: 389 | active: true 390 | IteratorNotThrowingNoSuchElementException: 391 | active: true 392 | LateinitUsage: 393 | active: false 394 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 395 | ignoreAnnotated: [] 396 | ignoreOnClassesPattern: '' 397 | MapGetWithNotNullAssertionOperator: 398 | active: false 399 | MissingPackageDeclaration: 400 | active: true 401 | NullCheckOnMutableProperty: 402 | active: true 403 | NullableToStringCall: 404 | active: false 405 | PropertyUsedBeforeDeclaration: 406 | active: true 407 | UnconditionalJumpStatementInLoop: 408 | active: true 409 | UnnecessaryNotNullCheck: 410 | active: true 411 | UnnecessaryNotNullOperator: 412 | active: true 413 | UnnecessarySafeCall: 414 | active: true 415 | UnreachableCatchBlock: 416 | active: false 417 | UnreachableCode: 418 | active: true 419 | UnsafeCallOnNullableType: 420 | active: true 421 | UnsafeCast: 422 | active: false 423 | UnusedUnaryOperator: 424 | active: false 425 | UselessPostfixExpression: 426 | active: true 427 | WrongEqualsTypeParameter: 428 | active: true 429 | 430 | style: 431 | active: true 432 | AlsoCouldBeApply: 433 | active: true 434 | BracesOnIfStatements: 435 | active: true 436 | BracesOnWhenStatements: 437 | active: true 438 | singleLine: 'necessary' 439 | multiLine: 'necessary' 440 | ClassOrdering: 441 | active: false 442 | CollapsibleIfStatements: 443 | active: false 444 | CanBeNonNullable: 445 | active: true 446 | CascadingCallWrapping: 447 | active: true 448 | DataClassContainsFunctions: 449 | active: false 450 | conversionFunctionPrefix: ['to'] 451 | DataClassShouldBeImmutable: 452 | active: true 453 | DestructuringDeclarationWithTooManyEntries: 454 | active: false 455 | maxDestructuringEntries: 3 456 | DoubleNegativeLambda: 457 | active: true 458 | EqualsNullCall: 459 | active: true 460 | EqualsOnSignatureLine: 461 | active: false 462 | ExplicitCollectionElementAccessMethod: 463 | active: true 464 | ExplicitItLambdaParameter: 465 | active: true 466 | ExpressionBodySyntax: 467 | active: true 468 | includeLineWrapping: true 469 | ForbiddenAnnotation: 470 | active: true 471 | ForbiddenComment: 472 | active: true 473 | comments: ['FIXME', 'STOPSHIP'] 474 | ForbiddenImport: 475 | active: false 476 | imports: [] 477 | forbiddenPatterns: '' 478 | ForbiddenMethodCall: 479 | active: false 480 | methods: ['kotlin.io.println', 'kotlin.io.print'] 481 | ForbiddenSuppress: 482 | active: true 483 | rules: [] 484 | ForbiddenVoid: 485 | active: true 486 | ignoreOverridden: true 487 | ignoreUsageInGenerics: true 488 | FunctionOnlyReturningConstant: 489 | active: false 490 | ignoreOverridableFunction: true 491 | ignoreActualFunction: true 492 | excludedFunctions: ['describeContents'] 493 | ignoreAnnotated: ['dagger.Provides'] 494 | LoopWithTooManyJumpStatements: 495 | active: false 496 | maxJumpCount: 1 497 | MagicNumber: 498 | active: false 499 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 500 | ignoreNumbers: ['-1', '0', '1', '2'] 501 | ignoreHashCodeFunction: true 502 | ignorePropertyDeclaration: false 503 | ignoreLocalVariableDeclaration: false 504 | ignoreConstantDeclaration: true 505 | ignoreCompanionObjectPropertyDeclaration: true 506 | ignoreAnnotation: false 507 | ignoreNamedArgument: true 508 | ignoreEnums: false 509 | ignoreRanges: false 510 | ignoreExtensionFunctions: true 511 | MandatoryBracesLoops: 512 | active: true 513 | MaxChainedCallsOnSameLine: 514 | active: true 515 | MaxLineLength: 516 | active: false # handled by ktlint 517 | maxLineLength: 120 518 | MayBeConst: 519 | active: true 520 | ModifierOrder: 521 | active: true 522 | MultilineLambdaItParameter: 523 | active: true 524 | MultilineRawStringIndentation: 525 | active: true 526 | indentSize: 4 527 | NestedClassesVisibility: 528 | active: true 529 | NewLineAtEndOfFile: 530 | active: true 531 | NoTabs: 532 | active: true 533 | NullableBooleanCheck: 534 | active: true 535 | ObjectLiteralToLambda: 536 | active: true 537 | OptionalAbstractKeyword: 538 | active: true 539 | OptionalUnit: 540 | active: true 541 | PreferToOverPairSyntax: 542 | active: true 543 | ProtectedMemberInFinalClass: 544 | active: true 545 | RedundantExplicitType: 546 | active: true 547 | RedundantHigherOrderMapUsage: 548 | active: false 549 | RedundantVisibilityModifierRule: 550 | active: false 551 | ReturnCount: 552 | active: true 553 | max: 2 554 | excludedFunctions: ['equals'] 555 | excludeLabeled: false 556 | excludeReturnFromLambda: true 557 | excludeGuardClauses: true 558 | SafeCast: 559 | active: true 560 | SerialVersionUIDInSerializableClass: 561 | active: true 562 | SpacingBetweenPackageAndImports: 563 | active: true 564 | StringShouldBeRawString: 565 | active: false 566 | ThrowsCount: 567 | active: true 568 | max: 3 569 | # excludeGuardClauses: true 570 | TrailingWhitespace: 571 | active: true 572 | TrimMultilineRawString: 573 | active: true 574 | UnderscoresInNumericLiterals: 575 | active: true 576 | acceptableLength: 4 577 | UnnecessaryAbstractClass: 578 | active: false 579 | ignoreAnnotated: ['dagger.Module'] 580 | UnnecessaryAnnotationUseSiteTarget: 581 | active: true 582 | UnnecessaryApply: 583 | active: true 584 | UnnecessaryBackticks: 585 | active: true 586 | UnnecessaryBracesAroundTrailingLambda: 587 | active: true 588 | UnnecessaryFilter: 589 | active: false 590 | UnnecessaryInheritance: 591 | active: true 592 | UnnecessaryInnerClass: 593 | active: true 594 | UnnecessaryLet: 595 | active: true 596 | UnnecessaryParentheses: 597 | active: true 598 | UntilInsteadOfRangeTo: 599 | active: true 600 | UnusedImports: 601 | active: true 602 | UnusedParameter: 603 | active: true 604 | UnusedPrivateClass: 605 | active: true 606 | UnusedPrivateMember: 607 | active: false 608 | allowedNames: '(_|ignored|expected|serialVersionUID)' 609 | UnusedPrivateProperty: 610 | active: true 611 | UseAnyOrNoneInsteadOfFind: 612 | active: true 613 | UseArrayLiteralsInAnnotations: 614 | active: true 615 | UseCheckOrError: 616 | active: true 617 | UseCheckNotNull: 618 | active: true 619 | UseDataClass: 620 | active: false 621 | ignoreAnnotated: [] 622 | allowVars: false 623 | UseEmptyCounterpart: 624 | active: true 625 | UseIfEmptyOrIfBlank: 626 | active: true 627 | UseIfInsteadOfWhen: 628 | active: false 629 | UseIsNullOrEmpty: 630 | active: true 631 | UseLet: 632 | active: true 633 | UseOrEmpty: 634 | active: true 635 | UseRequire: 636 | active: true 637 | UseRequireNotNull: 638 | active: true 639 | UseSumOfInsteadOfFlatMapSize: 640 | active: true 641 | UselessCallOnNotNull: 642 | active: true 643 | UtilityClassWithPublicConstructor: 644 | active: true 645 | VarCouldBeVal: 646 | active: true 647 | WildcardImport: 648 | active: true 649 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] 650 | excludeImports: [] 651 | 652 | style-eygraber: 653 | NewlineForMultilineKeyword: 654 | active: true 655 | autoCorrect: true 656 | 657 | Compose: 658 | CompositionLocalAllowlist: 659 | active: false 660 | ContentEmitterReturningValues: 661 | active: true 662 | # You can optionally add your own composables here 663 | # contentEmitters: MyComposable,MyOtherComposable 664 | Material2: 665 | active: true 666 | ModifierComposable: 667 | active: true 668 | ModifierMissing: 669 | active: true 670 | ModifierReused: 671 | active: true 672 | ModifierWithoutDefault: 673 | active: true 674 | MultipleEmitters: 675 | active: true 676 | # You can optionally add your own composables here 677 | # contentEmitters: MyComposable,MyOtherComposable 678 | MutableParams: 679 | active: true 680 | ComposableNaming: 681 | active: true 682 | # You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters) 683 | # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter 684 | ComposableParamOrder: 685 | active: true 686 | PreviewNaming: 687 | active: true 688 | PreviewPublic: 689 | active: true 690 | # You can optionally disable that only previews with @PreviewParameter are flagged 691 | # previewPublicOnlyIfParams: false 692 | RememberMissing: 693 | active: true 694 | UnstableCollections: 695 | active: true 696 | ViewModelForwarding: 697 | active: true 698 | ViewModelInjection: 699 | active: true 700 | -------------------------------------------------------------------------------- /format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | version=$(sed -n 's/ktlint = "\(.*\)"/\1/p' gradle/libs.versions.toml) 4 | url="https://github.com/pinterest/ktlint/releases/download/$version/ktlint" 5 | 6 | # Set the destination directory and file name 7 | destination_dir="tmp" 8 | file_name="ktlint-$version" 9 | 10 | mkdir -p $destination_dir 11 | 12 | # setting nullglob ensures proper behavior if nothing matches the glob 13 | shopt -s nullglob 14 | for file in $destination_dir/ktlint-*; do 15 | if [[ "$file" != "$destination_dir/$file_name" ]]; then 16 | rm "$file" 17 | fi 18 | done 19 | shopt -u nullglob 20 | 21 | # Check if the file already exists in the destination directory 22 | if [ ! -e "$destination_dir/$file_name" ]; then 23 | if command -v curl >/dev/null 2>&1; then 24 | curl -LJO "$url" 25 | mv "ktlint" "$destination_dir/$file_name" 26 | elif command -v wget >/dev/null 2>&1; then 27 | wget -O "$destination_dir/$file_name" "$url" 28 | else 29 | echo "Error: curl or wget not found. Please install either curl or wget." 30 | exit 1 31 | fi 32 | 33 | chmod +x "$destination_dir/$file_name" 34 | fi 35 | 36 | should_format=true 37 | for arg in "$@"; do 38 | if [ "$arg" == "--no-format" ]; then 39 | should_format=false 40 | set -- "${@//--no-format/}" 41 | break 42 | fi 43 | done 44 | 45 | args=() 46 | 47 | if [ "$should_format" = true ]; then 48 | args+=("--format") 49 | fi 50 | 51 | args+=("$@") 52 | 53 | "$destination_dir/$file_name" **/*.kt **/*.kts \!**/build/** \!Dangerfile.df.kts --color --color-name=YELLOW "${args[@]}" 54 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4g -XX:ReservedCodeCacheSize=240m -XX:+UseCompressedOops -XX:+UseParallelGC -XX:MetaspaceSize=256m -Dfile.encoding=UTF-8 2 | kotlin.daemon.jvm.options=-Xmx4g -XX:ReservedCodeCacheSize=240m -XX:+UseCompressedOops -XX:+UseParallelGC -XX:MetaspaceSize=256m -Dfile.encoding=UTF-8 3 | 4 | group=com.eygraber 5 | version=0.0.20-SNAPSHOT 6 | 7 | POM_URL=https://github.com/eygraber/compose-color-picker/ 8 | POM_SCM_URL=https://github.com/eygraber/compose-color-picker/ 9 | POM_SCM_CONNECTION=scm:git:git://github.com/eygraber/compose-color-picker.git 10 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/eygraber/compose-color-picker.git 11 | 12 | POM_LICENCE_NAME=MIT 13 | POM_LICENCE_URL=https://opensource.org/licenses/mit-license.php 14 | POM_LICENCE_DIST=repo 15 | 16 | POM_DEVELOPER_ID=eygraber 17 | POM_DEVELOPER_NAME=Eliezer Graber 18 | POM_DEVELOPER_URL=https://github.com/eygraber 19 | 20 | #Android 21 | android.useAndroidX=true 22 | android.enableJetifier=false 23 | android.enableR8.fullMode=true 24 | android.nonTransitiveRClass=true 25 | 26 | android.defaults.buildfeatures.aidl=false 27 | android.defaults.buildfeatures.buildconfig=false 28 | android.defaults.buildfeatures.renderscript=false 29 | android.defaults.buildfeatures.resvalues=false 30 | android.defaults.buildfeatures.shaders=false 31 | 32 | android.experimental.cacheCompileLibResources=true 33 | android.experimental.enableSourceSetPathsMap=true 34 | 35 | systemProp.org.gradle.android.cache-fix.ignoreVersionCheck=true 36 | 37 | #Compose 38 | org.jetbrains.compose.experimental.jscanvas.enabled=true 39 | org.jetbrains.compose.experimental.wasm.enabled=true 40 | 41 | # Gradle 42 | org.gradle.caching=true 43 | org.gradle.parallel=true 44 | org.gradle.configuration-cache=false 45 | # https://youtrack.jetbrains.com/issue/KT-55701 46 | org.gradle.configureondemand=false 47 | 48 | #Kotlin 49 | kotlin.incremental.wasm=true 50 | 51 | kotlin.js.yarn=false 52 | kotlin.native.enableKlibsCrossCompilation=true 53 | kotlin.native.ignoreDisabledTargets=true 54 | 55 | #Misc 56 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 57 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 58 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.10.1" 3 | 4 | android-sdk-compile = "35" 5 | android-sdk-target = "35" 6 | android-sdk-min = "21" 7 | 8 | androidx-activity = "1.10.1" 9 | 10 | composeJetbrains = "1.8.1" 11 | 12 | conventions = "0.0.84" 13 | 14 | detekt = "1.23.8" 15 | detektCompose = "0.4.22" 16 | detektEygraber = "1.0.11" 17 | 18 | dokka = "2.0.0" 19 | 20 | kotlin = "2.1.21" 21 | 22 | ktlint = "1.6.0" 23 | 24 | publish = "0.32.0" 25 | 26 | [plugins] 27 | conventions = { id = "com.eygraber.conventions", version.ref = "conventions" } 28 | 29 | [libraries] 30 | android-desugar = "com.android.tools:desugar_jdk_libs:2.1.5" 31 | 32 | androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } 33 | androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } 34 | androidx-appCompat = "androidx.appcompat:appcompat:1.7.1" 35 | androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } 36 | androidx-compose-material3 = { module = "androidx.compose.material3:material3" } 37 | androidx-compose-uiTooling = { module = "androidx.compose.ui:ui-tooling" } 38 | androidx-compose-uiToolingPreview = { module = "androidx.compose.ui:ui-tooling-preview" } 39 | 40 | buildscript-android = { module = "com.android.tools.build:gradle", version.ref = "agp" } 41 | buildscript-androidCacheFix = { module = "gradle.plugin.org.gradle.android:android-cache-fix-gradle-plugin", version = "3.0.1" } 42 | buildscript-compose-compiler = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } 43 | buildscript-compose-jetbrains = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeJetbrains" } 44 | buildscript-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } 45 | buildscript-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } 46 | buildscript-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 47 | buildscript-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "publish" } 48 | 49 | composeMaterialIcons = "org.jetbrains.compose.material:material-icons-core:1.7.3" 50 | 51 | detektCompose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" } 52 | detektEygraber-formatting = { module = "com.eygraber.detekt.rules:formatting", version.ref = "detektEygraber" } 53 | detektEygraber-style = { module = "com.eygraber.detekt.rules:style", version.ref = "detektEygraber" } 54 | 55 | # not actually used; just here so renovate picks it up 56 | ktlint = { module = "com.pinterest.ktlint:ktlint-bom", version.ref = "ktlint" } 57 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.eygraber.conventions-kotlin-multiplatform") 3 | id("com.eygraber.conventions-android-library") 4 | id("com.eygraber.conventions-compose-jetbrains") 5 | id("com.eygraber.conventions-detekt") 6 | id("com.eygraber.conventions-publish-maven-central") 7 | } 8 | 9 | android { 10 | namespace = "com.eygraber.compose.colorpicker" 11 | } 12 | 13 | kotlin { 14 | explicitApi() 15 | 16 | defaultKmpTargets( 17 | project = project, 18 | ) 19 | 20 | sourceSets { 21 | commonMain { 22 | dependencies { 23 | implementation(compose.foundation) 24 | implementation(compose.material3) 25 | implementation(compose.runtime) 26 | } 27 | } 28 | 29 | commonTest { 30 | dependencies { 31 | implementation(kotlin("test-common")) 32 | implementation(kotlin("test-annotations-common")) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=compose-color-picker 2 | POM_NAME=Compose Color Picker 3 | POM_DESCRIPTION=A Jetpack Compose color picking component 4 | -------------------------------------------------------------------------------- /library/src/commonMain/kotlin/com/eygraber/compose/colorpicker/ColorPicker.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.EnterTransition 5 | import androidx.compose.animation.ExitTransition 6 | import androidx.compose.foundation.Image 7 | import androidx.compose.foundation.gestures.awaitEachGesture 8 | import androidx.compose.foundation.gestures.awaitFirstDown 9 | import androidx.compose.foundation.gestures.drag 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.BoxWithConstraints 12 | import androidx.compose.foundation.layout.PaddingValues 13 | import androidx.compose.foundation.layout.aspectRatio 14 | import androidx.compose.foundation.shape.GenericShape 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.Immutable 17 | import androidx.compose.runtime.getValue 18 | import androidx.compose.runtime.mutableIntStateOf 19 | import androidx.compose.runtime.mutableStateOf 20 | import androidx.compose.runtime.remember 21 | import androidx.compose.runtime.setValue 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.geometry.Offset 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.graphics.isSpecified 26 | import androidx.compose.ui.input.pointer.pointerInput 27 | import androidx.compose.ui.unit.Dp 28 | import androidx.compose.ui.unit.dp 29 | 30 | @Immutable 31 | public data class ColorPickerState( 32 | val alpha: Float, 33 | val brightness: Float, 34 | val magnifier: ColorPicker.Magnifier, 35 | @Suppress("BooleanPropertyNaming") val resetSelectedPosition: Boolean, 36 | val colors: List, 37 | ) 38 | 39 | @Composable 40 | public fun rememberColorPickerState( 41 | alpha: Float = 1F, 42 | brightness: Float = 1F, 43 | magnifier: ColorPicker.Magnifier = ColorPicker.Magnifier.Default(), 44 | resetSelectedPosition: Boolean = false, 45 | @Suppress("UnstableCollections") colors: List = ColorWheel.DefaultColors, 46 | ): ColorPickerState = 47 | remember(alpha, brightness, magnifier, resetSelectedPosition, colors) { 48 | ColorPickerState( 49 | alpha, 50 | brightness, 51 | magnifier, 52 | resetSelectedPosition, 53 | colors, 54 | ) 55 | } 56 | 57 | @Composable 58 | public fun ColorPicker( 59 | onSelectedColor: (Color) -> Unit, 60 | modifier: Modifier = Modifier, 61 | state: ColorPickerState = rememberColorPickerState(), 62 | ) { 63 | BoxWithConstraints( 64 | modifier = modifier 65 | .aspectRatio(1F), 66 | ) { 67 | val diameter = constraints.maxWidth 68 | val radius = diameter / 2F 69 | 70 | var previousDiameter by remember { mutableIntStateOf(diameter) } 71 | var selectedPosition by remember { mutableStateOf(Offset.Zero) } 72 | var selectedPositionBeforeReset by remember { mutableStateOf(Offset.Zero) } 73 | var selectedColor by remember { mutableStateOf(Color.Unspecified) } 74 | 75 | val isSelectedAndDiameterChanged = 76 | selectedPosition != Offset.Zero && diameter != previousDiameter 77 | 78 | if(state.resetSelectedPosition) { 79 | if(selectedPosition != Offset.Zero) { 80 | selectedPositionBeforeReset = selectedPosition 81 | } 82 | selectedPosition = Offset.Zero 83 | } 84 | else if(isSelectedAndDiameterChanged) { 85 | selectedPosition = selectedPosition.translate( 86 | newDiameter = diameter, 87 | oldDiameter = previousDiameter, 88 | ) 89 | 90 | previousDiameter = diameter 91 | } 92 | 93 | val colorWheel = remember(diameter, state.alpha, state.brightness) { 94 | ColorWheel( 95 | diameter = diameter, 96 | radius = radius, 97 | alpha = state.alpha, 98 | brightness = state.brightness, 99 | colors = state.colors, 100 | ).apply { 101 | val currentColor = colorForPosition(selectedPosition) 102 | if(currentColor.isSpecified && currentColor != selectedColor) { 103 | selectedColor = currentColor 104 | onSelectedColor(currentColor) 105 | } 106 | } 107 | } 108 | 109 | val inputModifier = Modifier 110 | .pointerInput(diameter, state.alpha, state.brightness) { 111 | fun update(newPosition: Offset) { 112 | val clampedPosition = newPosition.clampToCircle(radius) 113 | if(clampedPosition != selectedPosition) { 114 | val newColor = colorWheel.colorForPosition(clampedPosition) 115 | if(newColor.isSpecified) { 116 | if(selectedColor != newColor) { 117 | selectedColor = newColor 118 | onSelectedColor(newColor) 119 | } 120 | selectedPosition = clampedPosition 121 | } 122 | } 123 | } 124 | 125 | awaitEachGesture { 126 | val down = awaitFirstDown() 127 | update(down.position) 128 | drag(down.id) { change -> 129 | change.consume() 130 | update(change.position) 131 | } 132 | } 133 | } 134 | 135 | Box( 136 | modifier = inputModifier, 137 | ) { 138 | Image(contentDescription = null, bitmap = colorWheel.image) 139 | 140 | if(state.magnifier is ColorPicker.Magnifier.Default) { 141 | val isMagnifierVisible = selectedColor.isSpecified && selectedPosition != Offset.Zero 142 | AnimatedVisibility( 143 | visible = isMagnifierVisible, 144 | enter = EnterTransition.None, 145 | exit = ExitTransition.None, 146 | ) { 147 | Magnifier( 148 | transitionData = updateMagnifierTransitionData(state.magnifier), 149 | magnifier = state.magnifier, 150 | position = { 151 | when(selectedPosition) { 152 | Offset.Zero -> selectedPositionBeforeReset 153 | else -> selectedPosition 154 | } 155 | }, 156 | color = { selectedColor }, 157 | ) 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | @Suppress("ktlint:standard:max-line-length") 165 | @Deprecated( 166 | message = "Use ColorPicker(Modifier, ColorPickerState, (Color) -> Unit)", 167 | level = DeprecationLevel.WARNING, 168 | replaceWith = ReplaceWith( 169 | "ColorPicker(modifier = modifier, state = rememberColorPickerState(alpha = alpha, brightness = brightness, magnifier = magnifier, resetSelectedPosition = resetSelectedPosition, ), onColorSelected, )", 170 | ), 171 | ) 172 | @Composable 173 | public fun ColorPicker( 174 | onSelectedColor: (Color) -> Unit, 175 | modifier: Modifier = Modifier, 176 | alpha: Float = 1F, 177 | brightness: Float = 1F, 178 | magnifier: ColorPicker.Magnifier = ColorPicker.Magnifier.Default(), 179 | resetSelectedPosition: Boolean = false, 180 | ) { 181 | ColorPicker( 182 | onSelectedColor = onSelectedColor, 183 | modifier = modifier, 184 | state = rememberColorPickerState( 185 | alpha = alpha, 186 | brightness = brightness, 187 | magnifier = magnifier, 188 | resetSelectedPosition = resetSelectedPosition, 189 | ), 190 | ) 191 | } 192 | 193 | public object ColorPicker { 194 | @Immutable 195 | public sealed class Magnifier { 196 | @Immutable 197 | public data object None : Magnifier() 198 | 199 | @Immutable 200 | public data class Default( 201 | val width: Dp = 150.dp, 202 | val height: Dp = 100.dp, 203 | val pillHeight: Dp = 50.dp, 204 | val pillColorWidthWeight: Float = .25F, 205 | val pillHexWidthWeight: Float = .75F, 206 | val shouldShowAlphaHex: Boolean = true, 207 | // more padding on bottom to account for the default shape 208 | val hexPadding: PaddingValues = PaddingValues( 209 | end = 5.dp, 210 | top = 10.dp, 211 | bottom = 20.dp, 212 | ), 213 | val selectionDiameter: Dp = 15.dp, 214 | val popupShape: GenericShape = MagnifierPopupShape, 215 | ) : Magnifier() 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /library/src/commonMain/kotlin/com/eygraber/compose/colorpicker/ColorWheel.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.graphics.Canvas 5 | import androidx.compose.ui.graphics.Color 6 | import androidx.compose.ui.graphics.ImageBitmap 7 | import androidx.compose.ui.graphics.Paint 8 | import androidx.compose.ui.graphics.RadialGradientShader 9 | import androidx.compose.ui.graphics.SweepGradientShader 10 | import androidx.compose.ui.graphics.toPixelMap 11 | 12 | /** 13 | * A color wheel with an [ImageBitmap] that draws a circular color wheel of the specified diameter. 14 | */ 15 | internal class ColorWheel( 16 | diameter: Int, 17 | radius: Float, 18 | alpha: Float, 19 | brightness: Float, 20 | colors: List, 21 | ) { 22 | private fun Color.applyBrightnessAndAlpha(alpha: Float, brightness: Float) = 23 | copy( 24 | red = red * brightness, 25 | green = green * brightness, 26 | blue = blue * brightness, 27 | alpha = alpha, 28 | ) 29 | 30 | private val sweepGradient = SweepGradientShader( 31 | colors = colors.map { it.applyBrightnessAndAlpha(alpha, brightness) }, 32 | colorStops = null, 33 | center = Offset(radius, radius), 34 | ) 35 | 36 | private val saturationGradient = RadialGradientShader( 37 | colors = listOf( 38 | Color.White.applyBrightnessAndAlpha(alpha, brightness), 39 | Color.Transparent, 40 | ), 41 | center = Offset(radius, radius), 42 | radius = radius, 43 | ) 44 | 45 | val image = ImageBitmap(diameter, diameter).also { imageBitmap -> 46 | val canvas = Canvas(imageBitmap) 47 | val center = Offset(radius, radius) 48 | val sweepPaint = Paint().apply { 49 | shader = sweepGradient 50 | } 51 | 52 | val saturationPaint = Paint().apply { 53 | shader = saturationGradient 54 | } 55 | 56 | canvas.drawCircle(center, radius, sweepPaint) 57 | canvas.drawCircle(center, radius, saturationPaint) 58 | } 59 | 60 | private val pixelMap by lazy { 61 | image.toPixelMap() 62 | } 63 | 64 | /** 65 | * @return the matching color for [position] inside [ColorWheel], or `null` if there is no color 66 | * or the color is partially transparent. 67 | */ 68 | fun colorForPosition(position: Offset): Color { 69 | val x = position.x.toInt() 70 | val y = position.y.toInt() 71 | with(pixelMap) { 72 | if(x !in 0 until width || y !in 0 until height) return Color.Unspecified 73 | return this[x, y].takeIf { it.alpha > 0F } ?: Color.Unspecified 74 | } 75 | } 76 | 77 | companion object { 78 | val DefaultColors = listOf( 79 | Color.Red, 80 | Color.Magenta, 81 | Color.Blue, 82 | Color.Cyan, 83 | Color.Green, 84 | Color.Yellow, 85 | Color.Red, 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/src/commonMain/kotlin/com/eygraber/compose/colorpicker/Magnifier.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.border 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.RowScope 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.offset 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.requiredHeight 14 | import androidx.compose.foundation.layout.requiredSize 15 | import androidx.compose.foundation.layout.requiredWidth 16 | import androidx.compose.foundation.layout.width 17 | import androidx.compose.foundation.shape.CircleShape 18 | import androidx.compose.foundation.shape.GenericShape 19 | import androidx.compose.material3.LocalTextStyle 20 | import androidx.compose.material3.Surface 21 | import androidx.compose.material3.Text 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.draw.alpha 26 | import androidx.compose.ui.draw.clip 27 | import androidx.compose.ui.draw.drawBehind 28 | import androidx.compose.ui.geometry.CornerRadius 29 | import androidx.compose.ui.geometry.Offset 30 | import androidx.compose.ui.geometry.RoundRect 31 | import androidx.compose.ui.graphics.Color 32 | import androidx.compose.ui.graphics.SolidColor 33 | import androidx.compose.ui.text.style.TextAlign 34 | import androidx.compose.ui.unit.IntOffset 35 | import androidx.compose.ui.unit.dp 36 | 37 | @Composable 38 | internal fun Magnifier( 39 | transitionData: MagnifierTransitionData, 40 | magnifier: ColorPicker.Magnifier.Default, 41 | position: () -> Offset, 42 | color: () -> Color, 43 | ) { 44 | val offsetModifier = Modifier.offset { 45 | val (x, y) = position() 46 | 47 | IntOffset( 48 | (x - magnifier.width.roundToPx() / 2).toInt(), 49 | // Align with the center of the selection circle 50 | (y - (magnifier.height.roundToPx() + magnifier.selectionDiameter.roundToPx()) / 2).toInt(), 51 | ) 52 | } 53 | 54 | Column( 55 | horizontalAlignment = Alignment.CenterHorizontally, 56 | modifier = offsetModifier 57 | .requiredHeight(magnifier.height) 58 | .requiredWidth(magnifier.width) 59 | .alpha(transitionData.alpha), 60 | ) { 61 | MagnifierPill( 62 | magnifier, 63 | color, 64 | modifier = Modifier.width(transitionData.pillWidth), 65 | ) 66 | 67 | MagnifierSelectionCircle( 68 | modifier = Modifier.requiredSize(transitionData.selectionDiameter), 69 | ) 70 | } 71 | } 72 | 73 | /** 74 | * Label representing the currently selected [colorProvider], with [Text] representing the hex code and a 75 | * square at the start showing the [colorProvider]. 76 | */ 77 | @Composable 78 | private fun MagnifierPill( 79 | options: ColorPicker.Magnifier.Default, 80 | colorProvider: () -> Color, 81 | modifier: Modifier = Modifier, 82 | ) { 83 | Surface( 84 | modifier = modifier, 85 | shape = options.popupShape, 86 | ) { 87 | Row { 88 | Box( 89 | modifier = Modifier 90 | .height(options.pillHeight) 91 | .weight(options.pillColorWidthWeight) 92 | .drawBehind { 93 | drawRect(colorProvider()) 94 | }, 95 | ) 96 | 97 | MagnifierLabel(options, colorProvider) 98 | } 99 | } 100 | } 101 | 102 | @Composable 103 | private fun RowScope.MagnifierLabel( 104 | options: ColorPicker.Magnifier.Default, 105 | colorProvider: () -> Color, 106 | ) { 107 | val text = colorProvider().toHexString(options.shouldShowAlphaHex) 108 | val textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center) 109 | Text( 110 | text = text, 111 | modifier = Modifier 112 | .weight(options.pillHexWidthWeight) 113 | .padding(options.hexPadding), 114 | style = textStyle, 115 | maxLines = 1, 116 | ) 117 | } 118 | 119 | /** 120 | * Selection circle drawn over the currently selected pixel of the color wheel. 121 | */ 122 | @Composable 123 | private fun MagnifierSelectionCircle( 124 | modifier: Modifier = Modifier, 125 | ) { 126 | Box( 127 | modifier = modifier 128 | .clip(CircleShape) 129 | .background(Color.Transparent) 130 | .border( 131 | border = BorderStroke(2.dp, SolidColor(Color.Black.copy(alpha = 0.75f))), 132 | shape = CircleShape, 133 | ), 134 | ) 135 | } 136 | 137 | /** 138 | * A [GenericShape] that draws a box with a triangle at the bottom center to indicate a popup. 139 | */ 140 | internal val MagnifierPopupShape = GenericShape { size, _ -> 141 | val width = size.width 142 | val height = size.height 143 | 144 | val arrowY = height * 0.8f 145 | val arrowXOffset = width * 0.4f 146 | 147 | addRoundRect(RoundRect(0f, 0f, width, arrowY, cornerRadius = CornerRadius(20f, 20f))) 148 | 149 | moveTo(arrowXOffset, arrowY) 150 | lineTo(width / 2f, height) 151 | lineTo(width - arrowXOffset, arrowY) 152 | close() 153 | } 154 | -------------------------------------------------------------------------------- /library/src/commonMain/kotlin/com/eygraber/compose/colorpicker/MagnifierTransitionData.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker 2 | 3 | import androidx.compose.animation.AnimatedVisibilityScope 4 | import androidx.compose.animation.EnterExitState 5 | import androidx.compose.animation.ExperimentalAnimationApi 6 | import androidx.compose.animation.core.animateDp 7 | import androidx.compose.animation.core.animateFloat 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.State 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.ui.unit.Dp 14 | import androidx.compose.ui.unit.dp 15 | 16 | internal class MagnifierTransitionData( 17 | pillWidth: State, 18 | selectionDiameter: State, 19 | alpha: State, 20 | ) { 21 | val pillWidth: Dp by pillWidth 22 | val selectionDiameter: Dp by selectionDiameter 23 | val alpha: Float by alpha 24 | } 25 | 26 | @OptIn(ExperimentalAnimationApi::class) 27 | @Composable 28 | internal fun AnimatedVisibilityScope.updateMagnifierTransitionData( 29 | options: ColorPicker.Magnifier.Default, 30 | ): MagnifierTransitionData { 31 | val labelWidth = transition.animateDp(transitionSpec = { tween() }) { 32 | if(it == EnterExitState.Visible) options.width else 0.dp 33 | } 34 | 35 | val magnifierDiameter = transition.animateDp(transitionSpec = { tween() }) { 36 | if(it == EnterExitState.Visible) options.selectionDiameter else 0.dp 37 | } 38 | 39 | val alpha = transition.animateFloat(transitionSpec = { tween() }) { 40 | if(it == EnterExitState.Visible) 1f else 0f 41 | } 42 | 43 | return remember(transition) { 44 | MagnifierTransitionData(labelWidth, magnifierDiameter, alpha) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /library/src/commonMain/kotlin/com/eygraber/compose/colorpicker/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.graphics.Color 5 | import kotlin.math.hypot 6 | 7 | internal fun Color.toHexString(includeAlpha: Boolean = true): String = buildString { 8 | append("#") 9 | if(includeAlpha) { 10 | append((alpha * 255).toInt().toString(radix = 16).padStart(length = 2, padChar = '0').uppercase()) 11 | } 12 | append((red * 255).toInt().toString(radix = 16).padStart(length = 2, padChar = '0').uppercase()) 13 | append((green * 255).toInt().toString(radix = 16).padStart(length = 2, padChar = '0').uppercase()) 14 | append((blue * 255).toInt().toString(radix = 16).padStart(length = 2, padChar = '0').uppercase()) 15 | } 16 | 17 | internal fun Offset.translate( 18 | newDiameter: Int, 19 | oldDiameter: Int, 20 | ): Offset { 21 | val multiplier = newDiameter / oldDiameter.toFloat() 22 | return Offset( 23 | x = x * multiplier, 24 | y = y * multiplier, 25 | ) 26 | } 27 | 28 | internal fun Offset.clampToCircle(radius: Float): Offset { 29 | val dx = x - radius 30 | val dy = y - radius 31 | val d = hypot(dx, dy) 32 | return when { 33 | d > radius -> Offset( 34 | x = radius + dx * (radius - 2) / d, 35 | y = radius + dy * (radius - 2) / d, 36 | ) 37 | else -> this 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "enabledManagers": ["gradle", "gradle-wrapper", "github-actions"], 6 | "labels": ["dependencies"], 7 | "prHourlyLimit": 3, 8 | "packageRules": [ 9 | { 10 | "groupName": "gradle-conventions", 11 | "matchPackagePrefixes": ["com.eygraber.conventions"], 12 | "automerge": true 13 | }, 14 | { 15 | "groupName": "gradle-develocity-plugin", 16 | "matchPackagePrefixes": ["com.gradle.develocity"], 17 | "automerge": true, 18 | "registryUrls": [ 19 | "https://plugins.gradle.org/m2" 20 | ] 21 | }, 22 | { 23 | "matchDatasources": ["maven"], 24 | "depType": "dependencies", 25 | "registryUrls": [ 26 | "https://repo.maven.apache.org/maven2/", 27 | "https://dl.google.com/dl/android/maven2/", 28 | "https://plugins.gradle.org/m2" 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /sample/android-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /sample/android-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | id("com.android.application") 4 | id("com.eygraber.conventions-kotlin-library") 5 | id("com.eygraber.conventions-detekt") 6 | id("com.eygraber.conventions-compose-jetbrains") 7 | } 8 | 9 | android { 10 | namespace = "com.eygraber.compose.colorpicker.sample" 11 | 12 | compileSdk = libs.versions.android.sdk.compile.get().toInt() 13 | 14 | defaultConfig { 15 | applicationId = "com.eygraber.compose.colorpicker.sample" 16 | targetSdk = libs.versions.android.sdk.target.get().toInt() 17 | minSdk = libs.versions.android.sdk.min.get().toInt() 18 | 19 | versionCode = 1 20 | versionName = "1.0.0" 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | multiDexEnabled = true 24 | } 25 | 26 | buildTypes { 27 | named("release") { 28 | isMinifyEnabled = true 29 | isShrinkResources = true 30 | proguardFiles.clear() 31 | proguardFiles += project.file("proguard-rules.pro") 32 | } 33 | 34 | named("debug") { 35 | applicationIdSuffix = ".debug" 36 | 37 | versionNameSuffix = "-DEBUG" 38 | 39 | isMinifyEnabled = false 40 | } 41 | } 42 | 43 | compileOptions { 44 | isCoreLibraryDesugaringEnabled = true 45 | } 46 | 47 | packaging { 48 | resources.pickFirsts += "META-INF/*" 49 | } 50 | 51 | dependencies { 52 | implementation(projects.sample.shared) 53 | 54 | coreLibraryDesugaring(libs.android.desugar) 55 | 56 | implementation(libs.androidx.activity) 57 | implementation(libs.androidx.activityCompose) 58 | implementation(libs.androidx.appCompat) 59 | 60 | implementation(libs.androidx.compose.foundation) 61 | implementation(libs.androidx.compose.material3) 62 | implementation(libs.androidx.compose.uiTooling) 63 | implementation(libs.androidx.compose.uiToolingPreview) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sample/android-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /sample/android-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/android-app/src/main/java/com/eygraber/compose/colorpicker/sample/SampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | class SampleActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | 11 | setContent { 12 | Sample() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eygraber/compose-color-picker/1219d4362c706cf5e44d82b51b0b9c04df4baa1a/sample/android-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /sample/android-app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Compose Color Picker Sample 3 | 4 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /sample/jvm-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | id("com.eygraber.conventions-compose-jetbrains") 4 | id("com.eygraber.conventions-kotlin-library") 5 | id("com.eygraber.conventions-detekt") 6 | } 7 | 8 | kotlin { 9 | dependencies { 10 | implementation(compose.desktop.currentOs) 11 | implementation(projects.sample.shared) 12 | } 13 | } 14 | 15 | compose.desktop { 16 | application { 17 | mainClass = "com.eygraber.compose.colorpicker.sample.ColorPickerJvmKt" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/jvm-app/src/main/kotlin/com/eygraber/compose/colorpicker/sample/ColorPickerJvm.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import androidx.compose.ui.window.singleWindowApplication 4 | 5 | fun main() { 6 | singleWindowApplication(title = "Color Picker Sample") { 7 | Sample() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.eygraber.conventions-kotlin-multiplatform") 3 | id("com.eygraber.conventions-android-library") 4 | id("com.eygraber.conventions-compose-jetbrains") 5 | id("com.eygraber.conventions-detekt") 6 | } 7 | 8 | android { 9 | namespace = "com.eygraber.compose.colorpicker.sample.shared" 10 | } 11 | 12 | kotlin { 13 | kmpTargets( 14 | KmpTarget.Android, 15 | KmpTarget.Js, 16 | KmpTarget.Jvm, 17 | KmpTarget.WasmJs, 18 | project = project, 19 | ignoreDefaultTargets = true, 20 | ) 21 | 22 | sourceSets { 23 | commonMain { 24 | dependencies { 25 | implementation(projects.library) 26 | 27 | api(compose.foundation) 28 | api(compose.material3) 29 | api(compose.runtime) 30 | 31 | api(libs.composeMaterialIcons) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/shared/src/androidMain/kotlin/com/eygraber/compose/colorpicker/sample/FixedDecimalCount.android.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import java.text.DecimalFormat 4 | 5 | private val digitCache = mutableMapOf() 6 | 7 | actual fun Float.toFixedDecimalCount(digits: Int): String = 8 | digitCache.getOrPut(digits) { DecimalFormat("0.${"0".repeat(digits)}") }.format(this) 9 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/eygraber/compose/colorpicker/sample/FixedDecimalCount.common.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | expect fun Float.toFixedDecimalCount(digits: Int): String 4 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/eygraber/compose/colorpicker/sample/RefreshIcon.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import androidx.compose.material.icons.materialIcon 4 | import androidx.compose.material.icons.materialPath 5 | 6 | internal val RefreshIcon by lazy { 7 | materialIcon(name = "Rounded.Refresh") { 8 | materialPath { 9 | moveTo(17.65f, 6.35f) 10 | curveToRelative(-1.63f, -1.63f, -3.94f, -2.57f, -6.48f, -2.31f) 11 | curveToRelative(-3.67f, 0.37f, -6.69f, 3.35f, -7.1f, 7.02f) 12 | curveTo(3.52f, 15.91f, 7.27f, 20.0f, 12.0f, 20.0f) 13 | curveToRelative(3.19f, 0.0f, 5.93f, -1.87f, 7.21f, -4.56f) 14 | curveToRelative(0.32f, -0.67f, -0.16f, -1.44f, -0.9f, -1.44f) 15 | curveToRelative(-0.37f, 0.0f, -0.72f, 0.2f, -0.88f, 0.53f) 16 | curveToRelative(-1.13f, 2.43f, -3.84f, 3.97f, -6.8f, 3.31f) 17 | curveToRelative(-2.22f, -0.49f, -4.01f, -2.3f, -4.48f, -4.52f) 18 | curveTo(5.31f, 9.44f, 8.26f, 6.0f, 12.0f, 6.0f) 19 | curveToRelative(1.66f, 0.0f, 3.14f, 0.69f, 4.22f, 1.78f) 20 | lineToRelative(-1.51f, 1.51f) 21 | curveToRelative(-0.63f, 0.63f, -0.19f, 1.71f, 0.7f, 1.71f) 22 | horizontalLineTo(19.0f) 23 | curveToRelative(0.55f, 0.0f, 1.0f, -0.45f, 1.0f, -1.0f) 24 | verticalLineTo(6.41f) 25 | curveToRelative(0.0f, -0.89f, -1.08f, -1.34f, -1.71f, -0.71f) 26 | lineToRelative(-0.64f, 0.65f) 27 | close() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/eygraber/compose/colorpicker/sample/Sample.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.BoxScope 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.ColumnScope 9 | import androidx.compose.foundation.layout.Row 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.layout.width 15 | import androidx.compose.foundation.shape.CornerSize 16 | import androidx.compose.material3.Card 17 | import androidx.compose.material3.Icon 18 | import androidx.compose.material3.IconButton 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.Slider 21 | import androidx.compose.material3.SliderDefaults 22 | import androidx.compose.material3.Text 23 | import androidx.compose.material3.darkColorScheme 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.getValue 26 | import androidx.compose.runtime.mutableFloatStateOf 27 | import androidx.compose.runtime.mutableStateOf 28 | import androidx.compose.runtime.remember 29 | import androidx.compose.runtime.setValue 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.graphics.isSpecified 34 | import androidx.compose.ui.unit.dp 35 | import com.eygraber.compose.colorpicker.ColorPicker 36 | import com.eygraber.compose.colorpicker.rememberColorPickerState 37 | import kotlinx.coroutines.DelicateCoroutinesApi 38 | import kotlinx.coroutines.GlobalScope 39 | import kotlinx.coroutines.delay 40 | import kotlinx.coroutines.launch 41 | 42 | @OptIn(DelicateCoroutinesApi::class) 43 | @Composable 44 | fun Sample( 45 | modifier: Modifier = Modifier, 46 | ) { 47 | SampleCard( 48 | modifier = modifier, 49 | ) { 50 | val defaultColor = MaterialTheme.colorScheme.onSurface 51 | 52 | var selectedColor by remember { mutableStateOf(defaultColor) } 53 | var alpha by remember { mutableFloatStateOf(1F) } 54 | var brightness by remember { mutableFloatStateOf(1F) } 55 | var shouldResetSelectedPosition by remember { mutableStateOf(false) } 56 | 57 | Column( 58 | modifier = Modifier.fillMaxSize(), 59 | horizontalAlignment = Alignment.CenterHorizontally, 60 | ) { 61 | Controls( 62 | selectedColor = selectedColor, 63 | alpha = alpha, 64 | brightness = brightness, 65 | defaultColor = defaultColor, 66 | onSelectedColorChange = { newSelectedColor -> 67 | selectedColor = newSelectedColor 68 | }, 69 | onAlphaChange = { newAlpha -> 70 | alpha = newAlpha 71 | }, 72 | onBrightnessChange = { newBrightness -> 73 | brightness = newBrightness 74 | }, 75 | resetSelectedPosition = { 76 | shouldResetSelectedPosition = true 77 | 78 | GlobalScope.launch { 79 | delay(500) 80 | shouldResetSelectedPosition = false 81 | } 82 | }, 83 | ) 84 | 85 | ColorPicker( 86 | onSelectedColor = { newSelectedColor -> 87 | selectedColor = when { 88 | newSelectedColor.isSpecified -> newSelectedColor 89 | else -> defaultColor 90 | } 91 | }, 92 | modifier = Modifier.weight(.66F), 93 | state = rememberColorPickerState( 94 | alpha = alpha, 95 | brightness = brightness, 96 | resetSelectedPosition = shouldResetSelectedPosition, 97 | ), 98 | ) 99 | } 100 | } 101 | } 102 | 103 | @Composable 104 | private fun ColumnScope.Controls( 105 | selectedColor: Color, 106 | defaultColor: Color, 107 | alpha: Float, 108 | brightness: Float, 109 | onSelectedColorChange: (Color) -> Unit, 110 | onAlphaChange: (Float) -> Unit, 111 | onBrightnessChange: (Float) -> Unit, 112 | resetSelectedPosition: () -> Unit, 113 | ) { 114 | Row( 115 | modifier = Modifier.weight(.33F).fillMaxWidth(), 116 | horizontalArrangement = Arrangement.SpaceBetween, 117 | ) { 118 | Column( 119 | verticalArrangement = Arrangement.SpaceAround, 120 | ) { 121 | Row( 122 | verticalAlignment = Alignment.CenterVertically, 123 | ) { 124 | Text( 125 | text = "Pick a color", 126 | color = selectedColor, 127 | ) 128 | 129 | IconButton( 130 | onClick = { 131 | onSelectedColorChange(defaultColor) 132 | onAlphaChange(1F) 133 | onBrightnessChange(1F) 134 | resetSelectedPosition() 135 | }, 136 | ) { 137 | Icon( 138 | imageVector = RefreshIcon, 139 | contentDescription = "Back to defaults", 140 | ) 141 | } 142 | } 143 | 144 | Box( 145 | modifier = Modifier 146 | .size(75.dp) 147 | .background(selectedColor), 148 | ) 149 | } 150 | 151 | Column( 152 | modifier = Modifier.align(Alignment.CenterVertically), 153 | ) { 154 | Text(text = "Alpha: ${alpha.toFixedDecimalCount(1)}") 155 | 156 | Slider( 157 | value = alpha, 158 | onValueChange = { 159 | onAlphaChange(it) 160 | }, 161 | valueRange = 0F..1F, 162 | steps = 0, 163 | colors = SliderDefaults.colors(thumbColor = selectedColor, activeTrackColor = selectedColor), 164 | modifier = Modifier.width(200.dp), 165 | ) 166 | 167 | Text(text = "Brightness: ${brightness.toFixedDecimalCount(1)}") 168 | 169 | Slider( 170 | value = brightness, 171 | onValueChange = { 172 | onBrightnessChange(it) 173 | }, 174 | valueRange = 0F..1F, 175 | steps = 0, 176 | colors = SliderDefaults.colors(thumbColor = selectedColor, activeTrackColor = selectedColor), 177 | modifier = Modifier.width(200.dp), 178 | ) 179 | } 180 | } 181 | } 182 | 183 | @Composable 184 | private fun SampleCard( 185 | modifier: Modifier = Modifier, 186 | content: @Composable BoxScope.() -> Unit, 187 | ) { 188 | MaterialTheme( 189 | colorScheme = darkColorScheme(), 190 | ) { 191 | Box( 192 | modifier = modifier 193 | .fillMaxSize() 194 | .background( 195 | color = MaterialTheme.colorScheme.background, 196 | ), 197 | ) { 198 | Card( 199 | shape = MaterialTheme.shapes.small.copy(all = CornerSize(8.dp)), 200 | modifier = Modifier 201 | .fillMaxSize() 202 | .padding(16.dp), 203 | ) { 204 | Box( 205 | modifier = Modifier.padding(16.dp), 206 | ) { 207 | content() 208 | } 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /sample/shared/src/jsMain/kotlin/com/eygraber/compose/colorpicker/sample/FixedDecimalCount.js.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | actual fun Float.toFixedDecimalCount(digits: Int): String = asDynamic().toFixed(digits).toString() 4 | -------------------------------------------------------------------------------- /sample/shared/src/jvmMain/kotlin/com/eygraber/compose/colorpicker/sample/FixedDecimalCount.jvm.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | import java.text.DecimalFormat 4 | 5 | private val digitCache = mutableMapOf() 6 | 7 | actual fun Float.toFixedDecimalCount(digits: Int): String = 8 | digitCache.getOrPut(digits) { DecimalFormat("0.${"0".repeat(digits)}") }.format(this) 9 | -------------------------------------------------------------------------------- /sample/shared/src/wasmJsMain/kotlin/com/eygraber/compose/colorpicker/sample/FixedDecimalCount.wasm.kt: -------------------------------------------------------------------------------- 1 | package com.eygraber.compose.colorpicker.sample 2 | 3 | @JsFun("(f, d) => f.toFixed(d)") 4 | external fun toFixed(f: Float, d: Int): String 5 | 6 | actual fun Float.toFixedDecimalCount(digits: Int): String = toFixed(this, digits) 7 | -------------------------------------------------------------------------------- /sample/webApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | 3 | plugins { 4 | id("com.eygraber.conventions-kotlin-multiplatform") 5 | id("com.eygraber.conventions-detekt") 6 | id("com.eygraber.conventions-compose-jetbrains") 7 | } 8 | 9 | kotlin { 10 | kmpTargets( 11 | KmpTarget.WasmJs, 12 | project = project, 13 | binaryType = BinaryType.Executable, 14 | webOptions = KmpTarget.WebOptions( 15 | isNodeEnabled = false, 16 | isBrowserEnabled = true, 17 | moduleName = "color-picker-wasm", 18 | ), 19 | ignoreDefaultTargets = true, 20 | ) 21 | @OptIn(ExperimentalWasmDsl::class) 22 | wasmJs { 23 | browser { 24 | commonWebpackConfig { 25 | outputFileName = "color-picker-wasm.js" 26 | experiments += "topLevelAwait" 27 | } 28 | } 29 | } 30 | 31 | sourceSets { 32 | wasmJsMain { 33 | dependencies { 34 | implementation(compose.foundation) 35 | implementation(compose.material3) 36 | 37 | implementation(projects.sample.shared) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/webApp/detekt.yml: -------------------------------------------------------------------------------- 1 | potential-bugs: 2 | active: true 3 | MissingPackageDeclaration: 4 | active: false 5 | -------------------------------------------------------------------------------- /sample/webApp/src/wasmJsMain/kotlin/main.wasm.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.CanvasBasedWindow 3 | import com.eygraber.compose.colorpicker.sample.Sample 4 | 5 | @OptIn(ExperimentalComposeUiApi::class) 6 | fun main() { 7 | CanvasBasedWindow("ColorPicker") { 8 | Sample() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/webApp/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Color Picker 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/webApp/src/wasmJsMain/resources/load.mjs: -------------------------------------------------------------------------------- 1 | import { instantiate } from 'color-picker-wasm.uninstantiated.mjs'; 2 | 3 | await wasmSetup; 4 | 5 | let te = null; 6 | try { 7 | await instantiate({ skia: Module['asm'] }); 8 | } catch (e) { 9 | te = e; 10 | } 11 | 12 | if (te != null) { 13 | throw te; 14 | } 15 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.eygraber.conventions.Env 2 | import com.eygraber.conventions.repositories.addCommonRepositories 3 | 4 | pluginManagement { 5 | repositories { 6 | google { 7 | content { 8 | includeGroupByRegex("com\\.google.*") 9 | includeGroupByRegex("com\\.android.*") 10 | includeGroupByRegex("androidx.*") 11 | } 12 | } 13 | 14 | mavenCentral() 15 | 16 | maven("https://oss.sonatype.org/content/repositories/snapshots") { 17 | mavenContent { 18 | snapshotsOnly() 19 | } 20 | } 21 | 22 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots") { 23 | mavenContent { 24 | snapshotsOnly() 25 | } 26 | } 27 | 28 | gradlePluginPortal() 29 | } 30 | } 31 | 32 | @Suppress("UnstableApiUsage") 33 | dependencyResolutionManagement { 34 | // comment this out for now because it doesn't work with KMP js 35 | // https://youtrack.jetbrains.com/issue/KT-55620/KJS-Gradle-plugin-doesnt-support-repositoriesMode 36 | // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 37 | 38 | repositories { 39 | addCommonRepositories( 40 | includeMavenCentral = true, 41 | includeMavenCentralSnapshots = true, 42 | includeGoogle = true, 43 | includeJetbrainsComposeDev = false, 44 | ) 45 | } 46 | } 47 | 48 | plugins { 49 | id("com.eygraber.conventions.settings") version "0.0.84" 50 | id("com.gradle.develocity") version "4.0.2" 51 | } 52 | 53 | rootProject.name = "compose-color-picker" 54 | 55 | include(":library") 56 | include(":sample:android-app") 57 | include(":sample:jvm-app") 58 | include(":sample:shared") 59 | include(":sample:webApp") 60 | 61 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 62 | 63 | develocity { 64 | buildScan { 65 | termsOfUseUrl = "https://gradle.com/terms-of-service" 66 | publishing.onlyIf { Env.isCI } 67 | if(Env.isCI) { 68 | termsOfUseAgree = "yes" 69 | } 70 | } 71 | } 72 | --------------------------------------------------------------------------------