diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml new file mode 100644 index 0000000..708fa17 --- /dev/null +++ b/.github/workflows/publish-android.yml @@ -0,0 +1,35 @@ +name: Publish Native Android Library + +on: workflow_dispatch + +jobs: + publish-android: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - name: set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + - name: Grant execute permission for publishing script + run: chmod +x ./scripts/publish-android.sh + - name: Make local props + run: | + cat << EOF > "local.properties" + centralTokenUsername=${{ secrets.ANDROID_CENTRAL_USERNAME }} + centralTokenPassword=${{ secrets.ANDROID_CENTRAL_PASSWORD }} + sonatypeStagingProfileId=${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }} + signing.keyId=${{ secrets.ANDROID_SIGNING_KEY_ID }} + signing.password=${{ secrets.ANDROID_SIGNING_PASSWORD }} + signing.key=${{ secrets.ANDROID_SIGNING_KEY }} + EOF + echo "local.properties file has been created successfully." + - name: Run publish script + working-directory: ./scripts + run: ./publish-android.sh \ No newline at end of file diff --git a/.github/workflows/github_actions.yml b/.github/workflows/unit_tests.yml similarity index 97% rename from .github/workflows/github_actions.yml rename to .github/workflows/unit_tests.yml index b445f27..dc5b779 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/unit_tests.yml @@ -1,4 +1,4 @@ -name: GitHub Actions +name: Run Unit Tests on: workflow_dispatch: diff --git a/build.gradle b/build.gradle index 9eeb234..4b75f43 100644 --- a/build.gradle +++ b/build.gradle @@ -4,14 +4,25 @@ buildscript { repositories { google() mavenCentral() + maven { + url = uri("https://plugins.gradle.org/m2/") + } } dependencies { + if (System.getenv("SHOULD_PUBLISH") == "true") { + classpath("io.github.gradle-nexus:publish-plugin:1.1.0") + } classpath 'com.android.tools.build:gradle:8.7.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jacoco:org.jacoco.core:$jacocoVersion" } } +if (System.getenv("SHOULD_PUBLISH") == "true") { + apply plugin: "io.github.gradle-nexus.publish-plugin" + apply from: file("./scripts/publish-root.gradle") +} + apply plugin: "com.android.library" apply plugin: "kotlin-android" apply plugin: "jacoco" @@ -65,6 +76,12 @@ android { testOptions { unitTests.returnDefaultValues = true } + + publishing { + singleVariant("release") { + withSourcesJar() + } + } } repositories { @@ -96,3 +113,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" } + +if (System.getenv("SHOULD_PUBLISH") == "true") { + apply from: file("./scripts/publish-module.gradle") +} diff --git a/docs/LICENSE b/docs/LICENSE index c2979b0..774efa0 100644 --- a/docs/LICENSE +++ b/docs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 OutSystems +Copyright (c) 2025 Ionic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a86319f --- /dev/null +++ b/pom.xml @@ -0,0 +1,10 @@ + + + + 4.0.0 + io.ionic.libs + ioncamera-android + 0.1.0 + \ No newline at end of file diff --git a/scripts/publish-android.sh b/scripts/publish-android.sh new file mode 100755 index 0000000..38b01a7 --- /dev/null +++ b/scripts/publish-android.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +ANDROID_PATH=../ +LOG_OUTPUT=./tmp/publish-android.txt +THE_VERSION=`sed -n 's/.*\(.*\)<\/version>.*/\1/p' ../pom.xml` + +# Get latest io.ionic:portals XML version info +PUBLISHED_URL="https://repo1.maven.org/maven2/io/ionic/libs/ioncamera-android/maven-metadata.xml" +PUBLISHED_DATA=$(curl -s $PUBLISHED_URL) +PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*(.*)<\/latest>.*/\1/;' <<< $PUBLISHED_DATA)" + +if [[ "$THE_VERSION" == "$PUBLISHED_VERSION" ]]; then + printf %"s\n\n" "Duplicate: a published version exists for $THE_VERSION, skipping..." +else + # Make log dir if doesnt exist + mkdir -p ./tmp + + # Export ENV variable used by Gradle for Versioning + export THE_VERSION + export SHOULD_PUBLISH=true + + printf %"s\n" "Attempting to build and publish version $THE_VERSION" + # Publish a release to the Maven repo + "$ANDROID_PATH"/gradlew clean build publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository --no-daemon --max-workers 1 -b "$ANDROID_PATH"/build.gradle -Pandroid.useAndroidX=true > $LOG_OUTPUT 2>&1 + # Stage a version + # "$ANDROID_PATH"/gradlew clean build publishReleasePublicationToSonatypeRepository --no-daemon --max-workers 1 -b "$ANDROID_PATH"/build.gradle -Pandroid.useAndroidX=true > $LOG_OUTPUT 2>&1 + + echo $RESULT + + if grep --quiet "BUILD SUCCESSFUL" $LOG_OUTPUT; then + printf %"s\n" "Success: Published to MavenCentral." + else + printf %"s\n" "Error publishing, check $LOG_OUTPUT for more info! Manually review and release from the Central Portal may be necessary https://central.sonatype.com/publishing/deployments/" + cat $LOG_OUTPUT + exit 1 + fi + +fi \ No newline at end of file diff --git a/scripts/publish-module.gradle b/scripts/publish-module.gradle new file mode 100644 index 0000000..2c08b84 --- /dev/null +++ b/scripts/publish-module.gradle @@ -0,0 +1,62 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +def LIB_VERSION = System.getenv('THE_VERSION') + +group = 'io.ionic.libs' +version = LIB_VERSION + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + // Coordinates + groupId 'io.ionic.libs' + artifactId 'ioncamera-android' + version LIB_VERSION + + // Two artifacts, the `aar` (or `jar`) and the sources + if (project.plugins.findPlugin("com.android.library")) { + from components.release + } else { + artifact("$buildDir/libs/${project.getName()}-${version}.jar") + } + + // POM Data + pom { + name = 'ioncamera-android' + description = 'Camera Android Lib' + url = 'https://github.com/ionic-team/ion-android-camera' + licenses { + license { + name = 'License' + url = 'https://github.com/ionic-team/ion-android-camera/blob/main/docs/LICENSE' + } + } + developers { + developer { + name = 'Ionic' + email = 'hi@ionic.io' + } + } + + // Version Control Info + scm { + connection = 'scm:git:github.com:ionic-team/ion-android-camera.git' + developerConnection = 'scm:git:ssh://github.com:ionic-team/ion-android-camera.git' + url = 'https://github.com/ionic-team/ion-android-camera/tree/main' + } + } + } + } + } + + signing { + useInMemoryPgpKeys( + rootProject.ext["signing.keyId"], + rootProject.ext["signing.key"], + rootProject.ext["signing.password"], + ) + sign publishing.publications + } +} \ No newline at end of file diff --git a/scripts/publish-root.gradle b/scripts/publish-root.gradle new file mode 100644 index 0000000..1270cd3 --- /dev/null +++ b/scripts/publish-root.gradle @@ -0,0 +1,37 @@ +// Create variables with empty default values +ext["signing.keyId"] = '' +ext["signing.key"] = '' +ext["signing.password"] = '' +ext["centralTokenUsername"] = '' +ext["centralTokenPassword"] = '' +ext["sonatypeStagingProfileId"] = '' + +File secretPropsFile = file('./local.properties') +if (secretPropsFile.exists()) { + // Read local.properties file first if it exists + Properties p = new Properties() + new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } + p.each { name, value -> ext[name] = value } +} else { + // Use system environment variables + ext["centralTokenUsername"] = System.getenv('ANDROID_CENTRAL_USERNAME') + ext["centralTokenPassword"] = System.getenv('ANDROID_CENTRAL_PASSWORD') + ext["sonatypeStagingProfileId"] = System.getenv('ANDROID_SONATYPE_STAGING_PROFILE_ID') + ext["signing.keyId"] = System.getenv('ANDROID_SIGNING_KEY_ID') + ext["signing.key"] = System.getenv('ANDROID_SIGNING_KEY') + ext["signing.password"] = System.getenv('ANDROID_SIGNING_PASSWORD') +} + +// Set up Sonatype repository +nexusPublishing { + repositories { + sonatype { + stagingProfileId = sonatypeStagingProfileId + username = centralTokenUsername + password = centralTokenPassword + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + } + } + repositoryDescription = 'IONCamera Android Lib v' + System.getenv('THE_VERSION') +} \ No newline at end of file diff --git a/src/main/kotlin/io/ionic/libs/ioncameralib/model/IONCAMRError.kt b/src/main/kotlin/io/ionic/libs/ioncameralib/model/IONCAMRError.kt index c861097..9928996 100644 --- a/src/main/kotlin/io/ionic/libs/ioncameralib/model/IONCAMRError.kt +++ b/src/main/kotlin/io/ionic/libs/ioncameralib/model/IONCAMRError.kt @@ -11,15 +11,15 @@ enum class IONCAMRError(val code: Int, val description: String) { EDIT_CANCELLED_ERROR(13, "Couldn't edit photo because the process was canceled."), CAPTURE_VIDEO_ERROR(16, "Couldn't record video."), CAPTURE_VIDEO_CANCELLED_ERROR(17, "Couldn't record video because the process was canceled."), - GENERIC_CHOOSE_MULTIMEDIA_ERROR(19, "Couldn't choose media from the gallery."), - CHOOSE_MULTIMEDIA_CANCELLED_ERROR(21, "Couldn't choose media from the gallery because the process was canceled."), - MEDIA_PATH_ERROR(22, "Couldn't get media file path."), - PLAY_VIDEO_GENERAL_ERROR(24, "Couldn't play video."), - EDIT_PICTURE_EMPTY_URI_ERROR(25, "URI parameter cannot be empty."), - FILE_DOES_NOT_EXIST_ERROR(28, "The selected file doesn't exist."), - FETCH_IMAGE_FROM_URI_ERROR(29, "Couldn't retrieve image from the URI."), - INVALID_ARGUMENT_ERROR(32, "Invalid argument provided to plugin method."), + GENERIC_CHOOSE_MULTIMEDIA_ERROR(18, "Couldn't choose media from the gallery."), + CHOOSE_MULTIMEDIA_CANCELLED_ERROR(20, "Couldn't choose media from the gallery because the process was canceled."), + MEDIA_PATH_ERROR(21, "Couldn't get media file path."), + PLAY_VIDEO_GENERAL_ERROR(23, "Couldn't play video."), + EDIT_PICTURE_EMPTY_URI_ERROR(24, "URI parameter cannot be empty."), + FILE_DOES_NOT_EXIST_ERROR(27, "The selected file doesn't exist."), + FETCH_IMAGE_FROM_URI_ERROR(28, "Couldn't retrieve image from the URI."), + INVALID_ARGUMENT_ERROR(31, "Invalid argument provided to plugin method."), // Overall Android specific - CONTEXT_ERROR(34, "Unable to get the context.") + CONTEXT_ERROR(33, "Unable to get the context.") } \ No newline at end of file